

let msgCache = {};
const webim = {

    login(loginInfo, listeners, options) {
    },

    syncMsgs(cbOk, cbErr) {
    },


    getC2CHistoryMsgs(options, cbOk, cbErr) {
    },

    syncGroupMsgs(options, cbOk, cbErr) {
    },

    sendMsg(msg, cbOk, cbErr) {
    },

    logout(cbOk, cbErr) {
    },

    setAutoRead(selSess, isOn, isResetAll) {
    },

    getProfilePortrait(options, cbOk, cbErr) {
    },

    setProfilePortrait(options, cbOk, cbErr) {
    },


    applyAddFriend(options, cbOk, cbErr) {
    },

    getPendency(options, cbOk, cbErr) {
    },

    deletePendency(options, cbOk, cbErr) {
    },

    responseFriend(options, cbOk, cbErr) {
    },

    getAllFriend(options, cbOk, cbErr) {
    },
    deleteFriend(options, cbOk, cbErr) {
    },

    addBlackList(options, cbOk, cbErr) {
    },

    getBlackList(options, cbOk, cbErr) {
    },

    deleteBlackList(options, cbOk, cbErr) {
    },

    uploadPic(options, cbOk, cbErr) {
    },


    createGroup(options, cbOk, cbErr) {
    },

    applyJoinGroup(options, cbOk, cbErr) {
    },


    handleApplyJoinGroup(options, cbOk, cbErr) {
    },

    deleteApplyJoinGroupPendency(options, cbOk, cbErr) {
    },


    quitGroup(options, cbOk, cbErr) {
    },

    getGroupPublicInfo(options, cbOk, cbErr) {
    },

    getGroupInfo(options, cbOk, cbErr) {
    },

    modifyGroupBaseInfo(options, cbOk, cbErr) {
    },

    destroyGroup(options, cbOk, cbErr) {
    },

    getJoinedGroupListHigh(options, cbOk, cbErr) {
    },

    getGroupMemberInfo(options, cbOk, cbErr) {
    },

    addGroupMember(options, cbOk, cbErr) {
    },

    modifyGroupMember(options, cbOk, cbErr) {
    },

    forbidSendMsg(options, cbOk, cbErr) {
    },


    deleteGroupMember(options, cbOk, cbErr) {
    },


    sendCustomGroupNotify(options, cbOk, cbErr) {
    },

    Msg(sess, isSend, seq, random, time, fromAccount, subType, fromAccountNick) {
    },

    MsgStore: {

        sessMap() {
            return {};
        },

        sessCount() {
            return 0;
        },


        sessByTypeId(type, id) {
            return {};
        },

        delSessByTypeId(type, id) {
            return true;
        },


        resetCookieAndSyncFlag() {
        },

        downloadMap: {},
    },

};


(function (webim) {
    const SDK = {
        VERSION: '1.7.0',
        APPID: '537048168',
    };


    let isAccessFormaEnvironment = true;


    const SRV_HOST = {
        FORMAL: {
            COMMON: 'https:',
            PIC: 'https:',
        },
        TEST: {
            COMMON: 'https:',
            PIC: 'https:',
        },
    };


    let BROWSER_INFO = {};

    let lowerBR = false;


    const SRV_NAME = {
        OPEN_IM: 'openim',
        GROUP: 'group_open_http_svc',
        FRIEND: 'sns',
        PROFILE: 'profile',
        RECENT_CONTACT: 'recentcontact',
        PIC: 'openpic',
        BIG_GROUP: 'group_open_http_noauth_svc',
        BIG_GROUP_LONG_POLLING: 'group_open_long_polling_http_noauth_svc',
        IM_OPEN_STAT: 'imopenstat',
    };


    const SRV_NAME_VER = {
        openim: 'v4',
        group_open_http_svc: 'v4',
        sns: 'v4',
        profile: 'v4',
        recentcontact: 'v4',
        openpic: 'v4',
        group_open_http_noauth_svc: 'v1',
        group_open_long_polling_http_noauth_svc: 'v1',
        imopenstat: 'v4',
    };


    const CMD_EVENT_ID_MAP = {
        login: 1,
        pic_up: 3,
        apply_join_group: 9,
        create_group: 10,
        longpolling: 18,
        send_group_msg: 19,
        sendmsg: 20,
    };


    const SESSION_TYPE = {
        C2C: 'C2C',
        GROUP: 'GROUP',
    };


    const RECENT_CONTACT_TYPE = {
        C2C: 1,
        GROUP: 2,
    };


    const MSG_MAX_LENGTH = {
        C2C: 12000,
        GROUP: 8898,
    };


    const ACTION_STATUS = {
        OK: 'OK',
        FAIL: 'FAIL',
    };

    const ERROR_CODE_CUSTOM = 99999;


    const MSG_ELEMENT_TYPE = {
        TEXT: 'TIMTextElem',
        FACE: 'TIMFaceElem',
        IMAGE: 'TIMImageElem',
        CUSTOM: 'TIMCustomElem',
        SOUND: 'TIMSoundElem',
        FILE: 'TIMFileElem',
        LOCATION: 'TIMLocationElem',
        GROUP_TIP: 'TIMGroupTipElem',
    };


    const IMAGE_TYPE = {
        ORIGIN: 1,
        LARGE: 2,
        SMALL: 3,
    };


    const UPLOAD_RES_PKG_FLAG = {
        RAW_DATA: 0,
        BASE64_DATA: 1,
    };


    const DOWNLOAD_FILE = {
        BUSSINESS_ID: '10001',
        AUTH_KEY: '617574686b6579',
        SERVER_IP: '182.140.186.147',
    };


    const DOWNLOAD_FILE_TYPE = {
        SOUND: 2106,
        FILE: 2107,
    };


    const UPLOAD_RES_TYPE = {
        IMAGE: 1,
        FILE: 2,
        SHORT_VIDEO: 3,
        SOUND: 4,
    };


    const VERSION_INFO = {
        APP_VERSION: '2.1',
        SERVER_VERSION: 1,
    };


    const LONG_POLLINNG_EVENT_TYPE = {
        C2C: 1,
        GROUP_COMMON: 3,
        GROUP_TIP: 4,
        GROUP_SYSTEM: 5,
        GROUP_TIP2: 6,
        FRIEND_NOTICE: 7,
        PROFILE_NOTICE: 8,
        C2C_COMMON: 9,
        C2C_EVENT: 10,
    };


    const C2C_MSG_SUB_TYPE = {
        COMMON: 0,
    };

    const C2C_EVENT_SUB_TYPE = {
        READED: 92,
    };


    const GROUP_MSG_SUB_TYPE = {
        COMMON: 0,
        LOVEMSG: 1,
        TIP: 2,
        REDPACKET: 3,
    };


    const GROUP_MSG_PRIORITY_TYPE = {
        REDPACKET: 1,
        COMMON: 2,
        LOVEMSG: 3,
    };


    const GROUP_TIP_TYPE = {
        JOIN: 1,
        QUIT: 2,
        KICK: 3,
        SET_ADMIN: 4,
        CANCEL_ADMIN: 5,
        MODIFY_GROUP_INFO: 6,
        MODIFY_MEMBER_INFO: 7,
    };


    const GROUP_TIP_MODIFY_GROUP_INFO_TYPE = {
        FACE_URL: 1,
        NAME: 2,
        OWNER: 3,
        NOTIFICATION: 4,
        INTRODUCTION: 5,
    };


    const GROUP_SYSTEM_TYPE = {
        JOIN_GROUP_REQUEST: 1,
        JOIN_GROUP_ACCEPT: 2,
        JOIN_GROUP_REFUSE: 3,
        KICK: 4,
        DESTORY: 5,
        CREATE: 6,
        INVITED_JOIN_GROUP_REQUEST: 7,
        QUIT: 8,
        SET_ADMIN: 9,
        CANCEL_ADMIN: 10,
        REVOKE: 11,
        READED: 15,
        CUSTOM: 255,
    };


    const FRIEND_NOTICE_TYPE = {
        FRIEND_ADD: 1,
        FRIEND_DELETE: 2,
        PENDENCY_ADD: 3,
        PENDENCY_DELETE: 4,
        BLACK_LIST_ADD: 5,
        BLACK_LIST_DELETE: 6,
        PENDENCY_REPORT: 7,
        FRIEND_UPDATE: 8,
    };


    const PROFILE_NOTICE_TYPE = {
        PROFILE_MODIFY: 1,
    };


    const TLS_ERROR_CODE = {
        OK: 0,
        SIGNATURE_EXPIRATION: 11,
    };


    const CONNECTION_STATUS = {
        INIT: -1,
        ON: 0,
        RECONNECT: 1,
        OFF: 9999,
    };

    const UPLOAD_PIC_BUSSINESS_TYPE = {
        GROUP_MSG: 1,
        C2C_MSG: 2,
        USER_HEAD: 3,
        GROUP_HEAD: 4,
    };

    const FRIEND_WRITE_MSG_ACTION = {
        ING: 14,
        STOP: 15,
    };


    const ajaxDefaultTimeOut = 15000;


    const OK_DELAY_TIME = 1000;


    const ERROR_DELAY_TIME = 5000;


    const GROUP_TIP_MAX_USER_COUNT = 10;


    let curLongPollingStatus = CONNECTION_STATUS.INIT;


    let longPollingOffCallbackFlag = false;


    let curLongPollingRetErrorCount = 0;


    let longPollingDefaultTimeOut = 60000;


    const longPollingIntervalTime = 5000;


    const longPollingTimeOutErrorCode = 60008;


    const longPollingKickedErrorCode = 91101;

    let LongPollingId = null;


    let curBigGroupLongPollingRetErrorCount = 0;


    const LONG_POLLING_MAX_RET_ERROR_COUNT = 10;


    let Upload_Retry_Times = 0;

    const Upload_Retry_Max_Times = 20;


    let jsonpRequestId = 0;

    let jsonpLastRspData = null;

    let jsonpCallback = null;

    let uploadResultIframeId = 0;

    let ipList = [];
    let authkey = null;
    let expireTime = null;


    const ERROR = {};

    let ctx = {
        sdkAppID: null,
        appIDAt3rd: null,
        accountType: null,
        identifier: null,
        tinyid: null,
        identifierNick: null,
        userSig: null,
        a2: null,
        contentType: 'json',
        apn: 1,
    };
    let opt = {};
    let xmlHttpObjSeq = 0;
    let xmlHttpObjMap = {};
    let curSeq = 0;
    let tempC2CMsgList = [];
    let tempC2CHistoryMsgList = [];

    const maxApiReportItemCount = 20;
    let apiReportItems = [];

    const Resources = {
        downloadMap: {},
    };


    const emotionDataIndexs = {
        '[惊讶]': 0,
        '[撇嘴]': 1,
        '[色]': 2,
        '[发呆]': 3,
        '[得意]': 4,
        '[流泪]': 5,
        '[害羞]': 6,
        '[闭嘴]': 7,
        '[睡]': 8,
        '[大哭]': 9,
        '[尴尬]': 10,
        '[发怒]': 11,
        '[调皮]': 12,
        '[龇牙]': 13,
        '[微笑]': 14,
        '[难过]': 15,
        '[酷]': 16,
        '[冷汗]': 17,
        '[抓狂]': 18,
        '[吐]': 19,
        '[偷笑]': 20,
        '[可爱]': 21,
        '[白眼]': 22,
        '[傲慢]': 23,
        '[饿]': 24,
        '[困]': 25,
        '[惊恐]': 26,
        '[流汗]': 27,
        '[憨笑]': 28,
        '[大兵]': 29,
        '[奋斗]': 30,
        '[咒骂]': 31,
        '[疑问]': 32,
        '[嘘]': 33,
        '[晕]': 34,
    };


    const emotions = {};

    const tool = new function () {
        this.formatTimeStamp = function (timestamp, format) {
            if (!timestamp) {
                return 0;
            }
            let formatTime;
            format = format || 'yyyy-MM-dd hh:mm:ss';
            const date = new Date(timestamp * 1000);
            const o = {
                'M+': date.getMonth() + 1,
                'd+': date.getDate(),
                'h+': date.getHours(),
                'm+': date.getMinutes(),
                's+': date.getSeconds(),
            };
            if (/(y+)/.test(format)) {
                formatTime = format.replace(RegExp.$1, (`${date.getFullYear()}`).substr(4 - RegExp.$1.length));
            } else {
                formatTime = format;
            }
            for (const k in o) {
                if (new RegExp(`(${k})`).test(formatTime)) { formatTime = formatTime.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((`00${o[k]}`).substr((`${o[k]}`).length))); }
            }
            return formatTime;
        };


        this.groupTypeEn2Ch = function (type_en) {
            let type_ch = null;
            switch (type_en) {
                case 'Public':
                    type_ch = '公开群';
                    break;
                case 'ChatRoom':
                    type_ch = '聊天室';
                    break;
                case 'Private':
                    type_ch = '讨论组';
                    break;
                case 'AVChatRoom':
                    type_ch = '直播聊天室';
                    break;
                default:
                    type_ch = type_en;
                    break;
            }
            return type_ch;
        };

        this.groupTypeCh2En = function (type_ch) {
            let type_en = null;
            switch (type_ch) {
                case '公开群':
                    type_en = 'Public';
                    break;
                case '聊天室':
                    type_en = 'ChatRoom';
                    break;
                case '讨论组':
                    type_en = 'Private';
                    break;
                case '直播聊天室':
                    type_en = 'AVChatRoom';
                    break;
                default:
                    type_en = type_ch;
                    break;
            }
            return type_en;
        };

        this.groupRoleEn2Ch = function (role_en) {
            let role_ch = null;
            switch (role_en) {
                case 'Member':
                    role_ch = '成员';
                    break;
                case 'Admin':
                    role_ch = '管理员';
                    break;
                case 'Owner':
                    role_ch = '群主';
                    break;
                default:
                    role_ch = role_en;
                    break;
            }
            return role_ch;
        };

        this.groupRoleCh2En = function (role_ch) {
            let role_en = null;
            switch (role_ch) {
                case '成员':
                    role_en = 'Member';
                    break;
                case '管理员':
                    role_en = 'Admin';
                    break;
                case '群主':
                    role_en = 'Owner';
                    break;
                default:
                    role_en = role_ch;
                    break;
            }
            return role_en;
        };

        this.groupMsgFlagEn2Ch = function (msg_flag_en) {
            let msg_flag_ch = null;
            switch (msg_flag_en) {
                case 'AcceptAndNotify':
                    msg_flag_ch = '接收并提示';
                    break;
                case 'AcceptNotNotify':
                    msg_flag_ch = '接收不提示';
                    break;
                case 'Discard':
                    msg_flag_ch = '屏蔽';
                    break;
                default:
                    msg_flag_ch = msg_flag_en;
                    break;
            }
            return msg_flag_ch;
        };

        this.groupMsgFlagCh2En = function (msg_flag_ch) {
            let msg_flag_en = null;
            switch (msg_flag_ch) {
                case '接收并提示':
                    msg_flag_en = 'AcceptAndNotify';
                    break;
                case '接收不提示':
                    msg_flag_en = 'AcceptNotNotify';
                    break;
                case '屏蔽':
                    msg_flag_en = 'Discard';
                    break;
                default:
                    msg_flag_en = msg_flag_ch;
                    break;
            }
            return msg_flag_en;
        };

        this.formatText2Html = function (text) {
            let html = text;
            if (html) {
                html = this.xssFilter(html);
                html = html.replace(/ /g, '&nbsp;');
                html = html.replace(/\n/g, '<br/>');
            }
            return html;
        };

        this.formatHtml2Text = function (html) {
            let text = html;
            if (text) {
                text = text.replace(/&nbsp;/g, ' ');
                text = text.replace(/<br\/>/g, '\n');
            }
            return text;
        };


        this.getStrBytes = function (str) {
            if (str == null || str === undefined) return 0;
            if (typeof str != 'string') {
                return 0;
            }
            let total = 0,
                charCode,
                i,
                len;
            for (i = 0, len = str.length; i < len; i++) {
                charCode = str.charCodeAt(i);
                if (charCode <= 0x007f) {
                    total += 1;
                } else if (charCode <= 0x07ff) {
                    total += 2;
                } else if (charCode <= 0xffff) {
                    total += 3;
                } else {
                    total += 4;
                }
            }
            return total;
        };


        this.xssFilter = function (val) {
            val = val.toString();
            val = val.replace(/[<]/g, '&lt;');
            val = val.replace(/[>]/g, '&gt;');
            val = val.replace(/"/g, '&quot;');

            return val;
        };


        this.trimStr = function (str) {
            if (!str) return '';
            str = str.toString();
            return str.replace(/(^\s*)|(\s*$)/g, '');
        };

        this.validNumber = function (str) {
            str = str.toString();
            return str.match(/(^\d{1,8}$)/g);
        };
        this.getReturnError = function (errorInfo, errorCode) {
            if (!errorCode) {
                errorCode = -100;
            }
            const error = {
                ActionStatus: ACTION_STATUS.FAIL,
                ErrorCode: errorCode,
                ErrorInfo: `${errorInfo}[${errorCode}]`,
            };
            return error;
        };


        this.setCookie = function (name, value, expires, path, domain) {
            const exp = new Date();
            exp.setTime(exp.getTime() + expires * 1000);
            document.cookie = `${name}=${escape(value)};expires=${exp.toGMTString()}`;
        };

        this.getCookie = function (name) {
            const result = document.cookie.match(new RegExp(`(^| )${name}=([^;]*)(;|$)`));
            if (result != null) {
                return unescape(result[2]);
            }
            return null;
        };

        this.delCookie = function (name) {
            const exp = new Date();
            exp.setTime(exp.getTime() - 1);
            const value = this.getCookie(name);
            if (value != null) { document.cookie = `${name}=${escape(value)};expires=${exp.toGMTString()}`; }
        };

        this.getBrowserInfo = function () {
            return {
                type: 'wechat app',
                ver: -1,
            };
        };
    }();


    const log = new function () {
        let on = true;

        this.setOn = function (onFlag) {
            on = onFlag;
        };

        this.getOn = function () {
            return on;
        };

        this.error = function (logStr) {
            try {
                on && console.error(logStr);
            } catch (e) {
            }
        };
        this.warn = function (logStr) {
            try {
                on && console.warn(logStr);
            } catch (e) {
            }
        };
        this.info = function (logStr) {
            try {
                on && console.info(logStr);
            } catch (e) {
            }
        };
        this.debug = function (logStr) {
            try {
                on && console.debug(logStr);
            } catch (e) {
            }
        };
    }();

    const unixtime = function (d) {
        if (!d) d = new Date();
        return Math.round(d.getTime() / 1000);
    };

    const fromunixtime = function (t) {
        return new Date(t * 1000);
    };

    const nextSeq = function () {
        if (curSeq) {
            curSeq += 1;
        } else {
            curSeq = Math.round(Math.random() * 10000000);
        }
        return curSeq;
    };

    const createRandom = function () {
        return Math.round(Math.random() * 4294967296);
    };


    const getXmlHttp = function () {
        let xmlhttp = null;
        if (window.XMLHttpRequest) {
            xmlhttp = new XMLHttpRequest();
        } else {
            try {
                xmlhttp = new ActiveXObject('Msxml2.XMLHTTP');
            } catch (e) {
                try {
                    xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e) {
                    return null;
                }
            }
        }
        return xmlhttp;
    };

    const ajaxRequest = function (meth, url, req, timeout, isLongPolling, cbOk, cbErr) {
        console.debug(url, req);
        wx.request({
            url,
            data: req,
            dataType: 'json',
            method: meth,
            header: {
                'Content-Type': 'application/json',
            },
            success(res) {
                curLongPollingRetErrorCount = curBigGroupLongPollingRetErrorCount = 0;
                if (cbOk) cbOk(res.data);
            },
            fail(res) {
                setTimeout(() => {
                    const errInfo = '请求服务器失败,请检查你的网络是否正常';
                    const error = tool.getReturnError(errInfo, -2);

                    if (cbErr) cbErr(error);
                }, 16);
            },
        });
    };

    const ajaxRequestJson = function (meth, url, req, timeout, isLongPolling, cbOk, cbErr) {
        ajaxRequest(meth, url, JSON.stringify(req), timeout, isLongPolling, (resp) => {
            let json = null;


            if (resp) json = resp;
            if (cbOk) cbOk(json);
        }, cbErr);
    };

    const isLogin = function () {
        return ctx.sdkAppID && ctx.identifier;
    };

    const checkLogin = function (cbErr, isNeedCallBack) {
        if (!isLogin()) {
            if (isNeedCallBack) {
                const errInfo = '请登录';
                const error = tool.getReturnError(errInfo, -4);

                if (cbErr) cbErr(error);
            }
            return false;
        }
        return true;
    };


    const isAccessFormalEnv = function () {
        return isAccessFormaEnvironment;
    };


    const getApiUrl = function (srvName, cmd, cbOk, cbErr) {
        let srvHost = SRV_HOST;
        if (isAccessFormalEnv()) {
            srvHost = SRV_HOST.FORMAL.COMMON;
        } else {
            srvHost = SRV_HOST.TEST.COMMON;
        }


        if (srvName == SRV_NAME.PIC) {
            if (isAccessFormalEnv()) {
                srvHost = SRV_HOST.FORMAL.PIC;
            } else {
                srvHost = SRV_HOST.TEST.PIC;
            }
        }

        let url = `${srvHost}/${SRV_NAME_VER[srvName]}/${srvName}/${cmd}?websdkappid=${SDK.APPID}&v=${SDK.VERSION}`;

        if (isLogin()) {
            if (cmd == 'login') {
                url += `&identifier=${encodeURIComponent(ctx.identifier)}&usersig=${ctx.userSig}`;
            } else if (ctx.tinyid && ctx.a2) {
                url += `&tinyid=${ctx.tinyid}&a2=${ctx.a2}`;
            } else if (cbErr) {
                log.error(`tinyid或a2为空[${srvName}][${cmd}]`);
                cbErr(tool.getReturnError(`tinyid或a2为空[${srvName}][${cmd}]`, -5));
                return false;
            }
            url += `&contenttype=${ctx.contentType}`;
        }
        url += `&sdkappid=${ctx.sdkAppID}&accounttype=${ctx.accountType}&apn=${ctx.apn}&reqtime=${unixtime()}`;
        return url;
    };


    const getSoundDownUrl = function (uuid, senderId) {
        let soundUrl = null;
        if (authkey && ipList[0]) {
            soundUrl = 'http:';
        } else {
            log.error('拼接语音下载url不报错：ip或者authkey为空');
        }
        return soundUrl;
    };


    const getFileDownUrl = function (uuid, senderId, fileName) {
        let fileUrl = null;
        if (authkey && ipList[0]) {
            fileUrl = 'http:';
        } else {
            log.error('拼接文件下载url不报错：ip或者authkey为空');
        }
        Resources.downloadMap[`uuid_${uuid}`] = fileUrl;
        return fileUrl;
    };


    const getFileDownUrlV2 = function (uuid, senderId, fileName, downFlag, receiverId, busiId, type) {
        const options = {
            From_Account: senderId,
            To_Account: receiverId,
            os_platform: 10,
            Timestamp: unixtime().toString(),
            Random: createRandom().toString(),
            request_info: [
                {
                    busi_id: busiId,
                    download_flag: downFlag,
                    type,
                    uuid,
                    version: VERSION_INFO.SERVER_VERSION,
                    auth_key: authkey,
                    ip: ipList[0],
                },
            ],
        };

        proto_applyDownload(options, (resp) => {
            if (resp.error_code == 0 && resp.response_info) {
                Resources.downloadMap[`uuid_${options.uuid}`] = resp.response_info.url;
            }
            if (onAppliedDownloadUrl) {
                onAppliedDownloadUrl({
                    uuid: options.uuid,
                    url: resp.response_info.url,
                    maps: Resources.downloadMap,
                });
            }
        }, (resp) => {
            log.error('获取下载地址失败', options.uuid);
        });
    };


    const clearXmlHttpObjMap = function () {
        for (const seq in xmlHttpObjMap) {
            const xmlHttpObj = xmlHttpObjMap[seq];
            if (xmlHttpObj) {
                xmlHttpObj.abort();
                xmlHttpObjMap[xmlHttpObjSeq] = null;
            }
        }
        xmlHttpObjSeq = 0;
        xmlHttpObjMap = {};
    };


    const clearSdk = function () {
        ctx = {
            sdkAppID: null,
            appIDAt3rd: null,
            accountType: null,
            identifier: null,
            identifierNick: null,
            userSig: null,
            contentType: 'json',
            apn: 1,
        };
        opt = {};

        curSeq = 0;


        jsonpRequestId = 0;

        jsonpLastRspData = null;

        apiReportItems = [];

        MsgManager.clear();
    };


    const _login = function (loginInfo, listeners, options, cbOk, cbErr) {
        clearSdk();

        if (options) opt = options;
        if (opt.isAccessFormalEnv == false) {
            isAccessFormaEnvironment = opt.isAccessFormalEnv;
        }
        if (opt.isLogOn == false) {
            log.setOn(opt.isLogOn);
        }


        if (!loginInfo) {
            if (cbErr) {
                cbErr(tool.getReturnError('loginInfo is empty', -6));
                return;
            }
        }
        if (!loginInfo.sdkAppID) {
            if (cbErr) {
                cbErr(tool.getReturnError('loginInfo.sdkAppID is empty', -7));
                return;
            }
        }
        if (!loginInfo.accountType) {
            if (cbErr) {
                cbErr(tool.getReturnError('loginInfo.accountType is empty', -8));
                return;
            }
        }

        if (loginInfo.identifier) {
            ctx.identifier = loginInfo.identifier.toString();
        }
        if (loginInfo.identifier && !loginInfo.userSig) {
            if (cbErr) {
                cbErr(tool.getReturnError('loginInfo.userSig is empty', -9));
                return;
            }
        }
        if (loginInfo.userSig) {
            ctx.userSig = loginInfo.userSig.toString();
        }
        ctx.sdkAppID = loginInfo.sdkAppID;
        ctx.accountType = loginInfo.accountType;

        if (ctx.identifier && ctx.userSig) {
            proto_login(
                (identifierNick) => {
                    MsgManager.init(
                        listeners,
                        (mmInitResp) => {
                            if (cbOk) {
                                mmInitResp.identifierNick = identifierNick;
                                cbOk(mmInitResp);
                            }
                        }, cbErr,
                    );
                },
                cbErr,
            );
        } else {
            MsgManager.init(
                listeners,
                cbOk,
                cbErr,
            );
        }
    };


    const initBrowserInfo = function () {
        BROWSER_INFO = tool.getBrowserInfo();
        log.info(`BROWSER_INFO: type=${BROWSER_INFO.type}, ver=${BROWSER_INFO.ver}`);
        if (BROWSER_INFO.type == 'ie') {
            if (parseInt(BROWSER_INFO.ver) < 10) {
                lowerBR = true;
            }
        }
    };


    const reportApiQuality = function (cmd, errorCode, errorInfo) {
        if (cmd == 'longpolling' && (errorCode == longPollingTimeOutErrorCode || errorCode == longPollingKickedErrorCode)) {
            return;
        }
        const eventId = CMD_EVENT_ID_MAP[cmd];
        if (eventId) {
            const reportTime = unixtime();
            let uniqKey = null;
            const msgCmdErrorCode = {
                Code: errorCode,
                ErrMsg: errorInfo,
            };
            if (ctx.a2) {
                uniqKey = `${ctx.a2.substring(0, 10)}_${reportTime}_${createRandom()}`;
            } else if (ctx.userSig) {
                uniqKey = `${ctx.userSig.substring(0, 10)}_${reportTime}_${createRandom()}`;
            }

            if (uniqKey) {
                const rptEvtItem = {
                    UniqKey: uniqKey,
                    EventId: eventId,
                    ReportTime: reportTime,
                    MsgCmdErrorCode: msgCmdErrorCode,
                };

                if (cmd == 'login') {
                    let loginApiReportItems = [];
                    loginApiReportItems.push(rptEvtItem);
                    const loginReportOpt = {
                        EvtItems: loginApiReportItems,
                        MainVersion: SDK.VERSION,
                        Version: '0',
                    };
                    proto_reportApiQuality(loginReportOpt,
                        (resp) => {
                            loginApiReportItems = null;
                        },
                        (err) => {
                            loginApiReportItems = null;
                        },
                    );
                } else {
                    apiReportItems.push(rptEvtItem);
                    if (apiReportItems.length >= maxApiReportItemCount) {
                        const reportOpt = {
                            EvtItems: apiReportItems,
                            MainVersion: SDK.VERSION,
                            Version: '0',
                        };
                        proto_reportApiQuality(reportOpt,
                            (resp) => {
                                apiReportItems = [];
                            },
                            (err) => {
                                apiReportItems = [];
                            },
                        );
                    }
                }
            }
        }
    };


    var proto_login = function (cbOk, cbErr) {
        ConnManager.apiCall(SRV_NAME.OPEN_IM, 'login', { State: 'Online' },
            (loginResp) => {
                if (loginResp.TinyId) {
                    ctx.tinyid = loginResp.TinyId;
                } else if (cbErr) {
                    cbErr(tool.getReturnError('TinyId is empty', -10));
                    return;
                }
                if (loginResp.A2Key) {
                    ctx.a2 = loginResp.A2Key;
                } else if (cbErr) {
                    cbErr(tool.getReturnError('A2Key is empty', -11));
                    return;
                }
                const tag_list = [
                    'Tag_Profile_IM_Nick',
                ];
                const options = {
                    From_Account: ctx.identifier,
                    To_Account: [ctx.identifier],
                    LastStandardSequence: 0,
                    TagList: tag_list,
                };
                proto_getProfilePortrait(
                    options,
                    (resp) => {
                        let nick,
                            gender,
                            allowType;
                        if (resp.UserProfileItem && resp.UserProfileItem.length > 0) {
                            for (const i in resp.UserProfileItem) {
                                for (const j in resp.UserProfileItem[i].ProfileItem) {
                                    switch (resp.UserProfileItem[i].ProfileItem[j].Tag) {
                                        case 'Tag_Profile_IM_Nick':
                                            nick = resp.UserProfileItem[i].ProfileItem[j].Value;
                                            if (nick) ctx.identifierNick = nick;
                                            break;
                                    }
                                }
                            }
                        }
                        if (cbOk) cbOk(ctx.identifierNick);
                    }, cbErr);
            }
            , cbErr);
    };

    const proto_logout = function (type, cbOk, cbErr) {
        if (!checkLogin(cbErr, false)) {
            clearSdk();
            if (cbOk) {
                cbOk({
                    ActionStatus: ACTION_STATUS.OK,
                    ErrorCode: 0,
                    ErrorInfo: 'logout success',
                });
            }
            return;
        }
        if (type == 'all') {
            ConnManager.apiCall(SRV_NAME.OPEN_IM, 'logout', {},
                (resp) => {
                    clearSdk();
                    if (cbOk) cbOk(resp);
                },
                cbErr);
        } else {
            ConnManager.apiCall(SRV_NAME.OPEN_IM, 'longpollinglogout', { LongPollingId },
                (resp) => {
                    clearSdk();
                    if (cbOk) cbOk(resp);
                },
                cbErr);
        }
    };

    const proto_sendMsg = function (msg, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        let msgInfo = null;

        switch (msg.sess.type()) {
            case SESSION_TYPE.C2C:
                msgInfo = {
                    From_Account: ctx.identifier,
                    To_Account: msg.sess.id().toString(),
                    MsgTimeStamp: msg.time,
                    MsgSeq: msg.seq,
                    MsgRandom: msg.random,
                    MsgBody: [],
                };
                break;
            case SESSION_TYPE.GROUP:
                var subType = msg.getSubType();
                msgInfo = {
                    GroupId: msg.sess.id().toString(),
                    From_Account: ctx.identifier,
                    Random: msg.random,
                    MsgBody: [],
                };
                switch (subType) {
                    case GROUP_MSG_SUB_TYPE.COMMON:
                        msgInfo.MsgPriority = 'COMMON';
                        break;
                    case GROUP_MSG_SUB_TYPE.REDPACKET:
                        msgInfo.MsgPriority = 'REDPACKET';
                        break;
                    case GROUP_MSG_SUB_TYPE.LOVEMSG:
                        msgInfo.MsgPriority = 'LOVEMSG';
                        break;
                    case GROUP_MSG_SUB_TYPE.TIP:
                        log.error(`不能主动发送群提示消息,subType=${subType}`);
                        break;
                    default:
                        log.error(`发送群消息时，出现未知子消息类型：subType=${subType}`);
                        return;
                        break;
                }
                break;
            default:
                break;
        }

        for (const i in msg.elems) {
            const elem = msg.elems[i];
            let msgContent = null;
            let msgType = elem.type;
            switch (msgType) {
                case MSG_ELEMENT_TYPE.TEXT:
                    msgContent = { Text: elem.content.text };
                    break;
                case MSG_ELEMENT_TYPE.FACE:
                    msgContent = { Index: elem.content.index, Data: elem.content.data };
                    break;
                case MSG_ELEMENT_TYPE.IMAGE:
                    var ImageInfoArray = [];
                    for (const j in elem.content.ImageInfoArray) {
                        ImageInfoArray.push(
                            {
                                Type: elem.content.ImageInfoArray[j].type,
                                Size: elem.content.ImageInfoArray[j].size,
                                Width: elem.content.ImageInfoArray[j].width,
                                Height: elem.content.ImageInfoArray[j].height,
                                URL: elem.content.ImageInfoArray[j].url,
                            },
                        );
                    }
                    msgContent = { UUID: elem.content.UUID, ImageInfoArray };
                    break;
                case MSG_ELEMENT_TYPE.SOUND:
                    log.warn('web端暂不支持发送语音消息');
                    continue;
                    break;
                case MSG_ELEMENT_TYPE.LOCATION:
                    log.warn('web端暂不支持发送地理位置消息');
                    continue;
                    break;
                case MSG_ELEMENT_TYPE.FILE:
                    msgContent = {
                        UUID: elem.content.uuid,
                        FileName: elem.content.name,
                        FileSize: elem.content.size,
                        DownloadFlag: elem.content.downFlag,
                    };
                    break;
                case MSG_ELEMENT_TYPE.CUSTOM:
                    msgContent = { Data: elem.content.data, Desc: elem.content.desc, Ext: elem.content.ext };
                    msgType = MSG_ELEMENT_TYPE.CUSTOM;
                    break;
                default :
                    log.warn(`web端暂不支持发送${elem.type}消息`);
                    continue;
                    break;
            }
            msgInfo.MsgBody.push({ MsgType: msgType, MsgContent: msgContent });
        }
        if (msg.sess.type() == SESSION_TYPE.C2C) {
            ConnManager.apiCall(SRV_NAME.OPEN_IM, 'sendmsg', msgInfo, cbOk, cbErr);
        } else if (msg.sess.type() == SESSION_TYPE.GROUP) {
            ConnManager.apiCall(SRV_NAME.GROUP, 'send_group_msg', msgInfo, cbOk, cbErr);
        }
    };

    const proto_longPolling = function (options, cbOk, cbErr) {
        if (!isAccessFormaEnvironment && typeof stopPolling != 'undefined' && stopPolling == true) {
            return;
        }
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.OPEN_IM, 'longpolling', options, cbOk, cbErr, longPollingDefaultTimeOut, true);
    };


    const proto_bigGroupLongPolling = function (options, cbOk, cbErr, timeout) {
        ConnManager.apiCall(SRV_NAME.BIG_GROUP_LONG_POLLING, 'get_msg', options, cbOk, cbErr, timeout);
    };


    var proto_getMsgs = function (cookie, syncFlag, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.OPEN_IM, 'getmsg', { Cookie: cookie, SyncFlag: syncFlag },
            (resp) => {
                if (resp.MsgList && resp.MsgList.length) {
                    for (const i in resp.MsgList) {
                        tempC2CMsgList.push(resp.MsgList[i]);
                    }
                }
                if (resp.SyncFlag == 1) {
                    proto_getMsgs(resp.Cookie, resp.SyncFlag, cbOk, cbErr);
                } else {
                    resp.MsgList = tempC2CMsgList;
                    tempC2CMsgList = [];
                    if (cbOk) cbOk(resp);
                }
            },
            cbErr);
    };

    const proto_c2CMsgReaded = function (cookie, c2CMsgReadedItem, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        const tmpC2CMsgReadedItem = [];
        for (const i in c2CMsgReadedItem) {
            const item = {
                To_Account: c2CMsgReadedItem[i].toAccount,
                LastedMsgTime: c2CMsgReadedItem[i].lastedMsgTime,
            };
            tmpC2CMsgReadedItem.push(item);
        }
        ConnManager.apiCall(SRV_NAME.OPEN_IM, 'msgreaded', {
            C2CMsgReaded: {
                Cookie: cookie,
                C2CMsgReadedItem: tmpC2CMsgReadedItem,
            },
        }, cbOk, cbErr);
    };


    const proto_deleteC2CMsg = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.OPEN_IM, 'deletemsg', options,
            cbOk, cbErr);
    };


    var proto_getC2CHistoryMsgs = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.OPEN_IM, 'getroammsg', options,
            (resp) => {
                const reqMsgCount = options.MaxCnt;
                const complete = resp.Complete;
                const rspMsgCount = resp.MaxCnt;
                const msgKey = resp.MsgKey;
                const lastMsgTime = resp.LastMsgTime;

                if (resp.MsgList && resp.MsgList.length) {
                    for (const i in resp.MsgList) {
                        tempC2CHistoryMsgList.push(resp.MsgList[i]);
                    }
                }
                let netxOptions = null;
                if (complete == 0) {
                    if (rspMsgCount < reqMsgCount) {
                        netxOptions = {
                            Peer_Account: options.Peer_Account,
                            MaxCnt: reqMsgCount - rspMsgCount,
                            LastMsgTime: lastMsgTime,
                            MsgKey: msgKey,
                        };
                    }
                }

                if (netxOptions) {
                    proto_getC2CHistoryMsgs(netxOptions, cbOk, cbErr);
                } else {
                    resp.MsgList = tempC2CHistoryMsgList;
                    tempC2CHistoryMsgList = [];
                    if (cbOk) cbOk(resp);
                }
            },
            cbErr);
    };


    const proto_createGroup = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        const opt = {

            Type: options.Type,

            Name: options.Name,
        };
        const member_list = [];


        for (let i = 0; i < options.MemberList.length; i++) {
            member_list.push({ Member_Account: options.MemberList[i] });
        }
        opt.MemberList = member_list;

        if (options.GroupId) {
            opt.GroupId = options.GroupId;
        }

        if (options.Owner_Account) {
            opt.Owner_Account = options.Owner_Account;
        }

        if (options.Introduction) {
            opt.Introduction = options.Introduction;
        }

        if (options.Notification) {
            opt.Notification = options.Notification;
        }

        if (options.MaxMemberCount) {
            opt.MaxMemberCount = options.MaxMemberCount;
        }

        if (options.ApplyJoinOption) {
            opt.ApplyJoinOption = options.ApplyJoinOption;
        }

        if (options.AppDefinedData) {
            opt.AppDefinedData = options.AppDefinedData;
        }

        if (options.FaceUrl) {
            opt.FaceUrl = options.FaceUrl;
        }
        ConnManager.apiCall(SRV_NAME.GROUP, 'create_group', opt,
            cbOk, cbErr);
    };


    const proto_createGroupHigh = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.GROUP, 'create_group', options,
            cbOk, cbErr);
    };


    const proto_modifyGroupBaseInfo = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'modify_group_base_info', options,
            cbOk, cbErr);
    };


    const proto_applyJoinGroup = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'apply_join_group', {
            GroupId: options.GroupId,
            ApplyMsg: options.ApplyMsg,
            UserDefinedField: options.UserDefinedField,
        },
            cbOk, cbErr);
    };


    const proto_applyJoinBigGroup = function (options, cbOk, cbErr) {
        let srvName;
        if (!checkLogin(cbErr, false)) {
            srvName = SRV_NAME.BIG_GROUP;
        } else {
            srvName = SRV_NAME.GROUP;
        }
        ConnManager.apiCall(srvName, 'apply_join_group', {
            GroupId: options.GroupId,
            ApplyMsg: options.ApplyMsg,
            UserDefinedField: options.UserDefinedField,
        },
            (resp) => {
                if (resp.JoinedStatus && resp.JoinedStatus == 'JoinedSuccess') {
                    if (resp.LongPollingKey) {
                        MsgManager.setBigGroupLongPollingOn(true);
                        MsgManager.setBigGroupLongPollingKey(resp.LongPollingKey);
                        MsgManager.setBigGroupLongPollingMsgMap(options.GroupId, 0);
                        MsgManager.bigGroupLongPolling();
                    } else {
                        cbErr && cbErr(tool.getReturnError(`The type of group is not AVChatRoom: groupid=${options.GroupId}`, -12));
                        return;
                    }
                }
                if (cbOk) cbOk(resp);
            }
            , (err) => {
                if (cbErr) cbErr(err);
            });
    };


    const proto_handleApplyJoinGroupPendency = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'handle_apply_join_group', {
            GroupId: options.GroupId,
            Applicant_Account: options.Applicant_Account,
            HandleMsg: options.HandleMsg,
            Authentication: options.Authentication,
            MsgKey: options.MsgKey,
            ApprovalMsg: options.ApprovalMsg,
            UserDefinedField: options.UserDefinedField,
        },
            cbOk,
            (err) => {
                if (err.ErrorCode == 10024) {
                    if (cbOk) {
                        const resp = {
                            ActionStatus: ACTION_STATUS.OK,
                            ErrorCode: 0,
                            ErrorInfo: '该申请已经被处理过',
                        };
                        cbOk(resp);
                    }
                } else if (cbErr) cbErr(err);
            },
        );
    };


    const proto_quitGroup = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'quit_group', {
            GroupId: options.GroupId,
        },
            cbOk, cbErr);
    };


    const proto_quitBigGroup = function (options, cbOk, cbErr) {
        let srvName;
        if (!checkLogin(cbErr, false)) {
            srvName = SRV_NAME.BIG_GROUP;
        } else {
            srvName = SRV_NAME.GROUP;
        }
        ConnManager.apiCall(srvName, 'quit_group',
            { GroupId: options.GroupId },
            (resp) => {
                MsgManager.resetBigGroupLongPollingInfo();
                if (cbOk) cbOk(resp);
            },
            cbErr);
    };

    const proto_searchGroupByName = function (options, cbOk, cbErr) {
        ConnManager.apiCall(SRV_NAME.GROUP, 'search_group', options, cbOk, cbErr);
    };


    const proto_getGroupPublicInfo = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'get_group_public_info', {
            GroupIdList: options.GroupIdList,
            ResponseFilter: {
                GroupBasePublicInfoFilter: options.GroupBasePublicInfoFilter,
            },
        },
            (resp) => {
                resp.ErrorInfo = '';
                if (resp.GroupInfo) {
                    for (const i in resp.GroupInfo) {
                        const errorCode = resp.GroupInfo[i].ErrorCode;
                        if (errorCode > 0) {
                            resp.ActionStatus = ACTION_STATUS.FAIL;
                            resp.GroupInfo[i].ErrorInfo = `[${errorCode}]${resp.GroupInfo[i].ErrorInfo}`;
                            resp.ErrorInfo += `${resp.GroupInfo[i].ErrorInfo}\n`;
                        }
                    }
                }
                if (resp.ActionStatus == ACTION_STATUS.FAIL) {
                    if (cbErr) {
                        cbErr(resp);
                    }
                } else if (cbOk) {
                    cbOk(resp);
                }
            },
            cbErr);
    };


    const proto_getGroupInfo = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        const opt = {
            GroupIdList: options.GroupIdList,
            ResponseFilter: {
                GroupBaseInfoFilter: options.GroupBaseInfoFilter,
                MemberInfoFilter: options.MemberInfoFilter,
            },
        };
        if (options.AppDefinedDataFilter_Group) {
            opt.ResponseFilter.AppDefinedDataFilter_Group = options.AppDefinedDataFilter_Group;
        }
        if (options.AppDefinedDataFilter_GroupMember) {
            opt.ResponseFilter.AppDefinedDataFilter_GroupMember = options.AppDefinedDataFilter_GroupMember;
        }
        ConnManager.apiCall(SRV_NAME.GROUP, 'get_group_info', opt,
            cbOk, cbErr);
    };


    const proto_getGroupMemberInfo = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'get_group_member_info', {
            GroupId: options.GroupId,
            Offset: options.Offset,
            Limit: options.Limit,
            MemberInfoFilter: options.MemberInfoFilter,
            MemberRoleFilter: options.MemberRoleFilter,
            AppDefinedDataFilter_GroupMember: options.AppDefinedDataFilter_GroupMember,
        },
            cbOk, cbErr);
    };


    const proto_addGroupMember = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'add_group_member', {
            GroupId: options.GroupId,
            Silence: options.Silence,
            MemberList: options.MemberList,
        },
            cbOk, cbErr);
    };


    const proto_modifyGroupMember = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        const opt = {};
        if (options.GroupId) {
            opt.GroupId = options.GroupId;
        }
        if (options.Member_Account) {
            opt.Member_Account = options.Member_Account;
        }

        if (options.Role) {
            opt.Role = options.Role;
        }

        if (options.MsgFlag) {
            opt.MsgFlag = options.MsgFlag;
        }
        if (options.ShutUpTime) {
            opt.ShutUpTime = options.ShutUpTime;
        }
        if (options.NameCard) {
            opt.NameCard = options.NameCard;
        }
        if (options.AppMemberDefinedData) {
            opt.AppMemberDefinedData = options.AppMemberDefinedData;
        }
        ConnManager.apiCall(SRV_NAME.GROUP, 'modify_group_member_info', opt,
            cbOk, cbErr);
    };


    const proto_deleteGroupMember = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'delete_group_member', {
            GroupId: options.GroupId,
            Silence: options.Silence,
            MemberToDel_Account: options.MemberToDel_Account,
            Reason: options.Reason,
        },
            cbOk, cbErr);
    };


    const proto_destroyGroup = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'destroy_group', {
            GroupId: options.GroupId,
        },
            cbOk, cbErr);
    };


    const proto_changeGroupOwner = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.GROUP, 'change_group_owner', options, cbOk, cbErr);
    };


    const proto_getJoinedGroupListHigh = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'get_joined_group_list', {
            Member_Account: options.Member_Account,
            Limit: options.Limit,
            Offset: options.Offset,
            GroupType: options.GroupType,
            ResponseFilter: {
                GroupBaseInfoFilter: options.GroupBaseInfoFilter,
                SelfInfoFilter: options.SelfInfoFilter,
            },
        },
            cbOk, cbErr);
    };


    const proto_getRoleInGroup = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'get_role_in_group', {
            GroupId: options.GroupId,
            User_Account: options.User_Account,
        },
            cbOk, cbErr);
    };


    const proto_forbidSendMsg = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;

        ConnManager.apiCall(SRV_NAME.GROUP, 'forbid_send_msg', {
            GroupId: options.GroupId,
            Members_Account: options.Members_Account,
            ShutUpTime: options.ShutUpTime,
        },
            cbOk, cbErr);
    };


    const proto_sendCustomGroupNotify = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.GROUP, 'send_group_system_notification', options,
            cbOk, cbErr);
    };


    const proto_getGroupMsgs = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.GROUP, 'group_msg_get', {
            GroupId: options.GroupId,
            ReqMsgSeq: options.ReqMsgSeq,
            ReqMsgNumber: options.ReqMsgNumber,
        },
            cbOk, cbErr);
    };

    const proto_groupMsgReaded = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.GROUP, 'msg_read_report', {
            GroupId: options.GroupId,
            MsgReadedSeq: options.MsgReadedSeq,
        },
            cbOk, cbErr);
    };


    const convertErrorEn2ZhFriend = function (resp) {
        let errorAccount = [];
        if (resp.Fail_Account && resp.Fail_Account.length) {
            errorAccount = resp.Fail_Account;
        }
        if (resp.Invalid_Account && resp.Invalid_Account.length) {
            for (const k in resp.Invalid_Account) {
                errorAccount.push(resp.Invalid_Account[k]);
            }
        }
        if (errorAccount.length) {
            resp.ActionStatus = ACTION_STATUS.FAIL;
            resp.ErrorCode = ERROR_CODE_CUSTOM;
            resp.ErrorInfo = '';
            for (const i in errorAccount) {
                const failCount = errorAccount[i];
                for (const j in resp.ResultItem) {
                    if (resp.ResultItem[j].To_Account == failCount) {
                        const resultCode = resp.ResultItem[j].ResultCode;
                        resp.ResultItem[j].ResultInfo = `[${resultCode}]${resp.ResultItem[j].ResultInfo}`;
                        resp.ErrorInfo += `${resp.ResultItem[j].ResultInfo}\n`;
                        break;
                    }
                }
            }
        }
        return resp;
    };

    const proto_applyAddFriend = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'friend_add', {
            From_Account: ctx.identifier,
            AddFriendItem: options.AddFriendItem,
        },
            (resp) => {
                const newResp = convertErrorEn2ZhFriend(resp);
                if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
                    if (cbErr) cbErr(newResp);
                } else if (cbOk) {
                    cbOk(newResp);
                }
            }, cbErr);
    };

    const proto_deleteFriend = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'friend_delete', {
            From_Account: ctx.identifier,
            To_Account: options.To_Account,
            DeleteType: options.DeleteType,
        },
            (resp) => {
                const newResp = convertErrorEn2ZhFriend(resp);
                if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
                    if (cbErr) cbErr(newResp);
                } else if (cbOk) {
                    cbOk(newResp);
                }
            }, cbErr);
    };

    const proto_getPendency = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'pendency_get', {
            From_Account: ctx.identifier,
            PendencyType: options.PendencyType,
            StartTime: options.StartTime,
            MaxLimited: options.MaxLimited,
            LastSequence: options.LastSequence,
        },
            cbOk, cbErr);
    };

    const proto_deletePendency = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'pendency_delete', {
            From_Account: ctx.identifier,
            PendencyType: options.PendencyType,
            To_Account: options.To_Account,

        },
            (resp) => {
                const newResp = convertErrorEn2ZhFriend(resp);
                if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
                    if (cbErr) cbErr(newResp);
                } else if (cbOk) {
                    cbOk(newResp);
                }
            }, cbErr);
    };

    const proto_responseFriend = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'friend_response', {
            From_Account: ctx.identifier,
            ResponseFriendItem: options.ResponseFriendItem,
        },
            (resp) => {
                const newResp = convertErrorEn2ZhFriend(resp);
                if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
                    if (cbErr) cbErr(newResp);
                } else if (cbOk) {
                    cbOk(newResp);
                }
            }, cbErr);
    };

    const proto_getAllFriend = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'friend_get_all', {
            From_Account: ctx.identifier,
            TimeStamp: options.TimeStamp,
            StartIndex: options.StartIndex,
            GetCount: options.GetCount,
            LastStandardSequence: options.LastStandardSequence,
            TagList: options.TagList,
        },
            cbOk, cbErr);
    };


    var proto_getProfilePortrait = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.PROFILE, 'portrait_get', {
            From_Account: ctx.identifier,
            To_Account: options.To_Account,

            TagList: options.TagList,
        },
            (resp) => {
                let errorAccount = [];
                if (resp.Fail_Account && resp.Fail_Account.length) {
                    errorAccount = resp.Fail_Account;
                }
                if (resp.Invalid_Account && resp.Invalid_Account.length) {
                    for (const k in resp.Invalid_Account) {
                        errorAccount.push(resp.Invalid_Account[k]);
                    }
                }
                if (errorAccount.length) {
                    resp.ActionStatus = ACTION_STATUS.FAIL;
                    resp.ErrorCode = ERROR_CODE_CUSTOM;
                    resp.ErrorInfo = '';
                    for (const i in errorAccount) {
                        const failCount = errorAccount[i];
                        for (const j in resp.UserProfileItem) {
                            if (resp.UserProfileItem[j].To_Account == failCount) {
                                const resultCode = resp.UserProfileItem[j].ResultCode;
                                resp.UserProfileItem[j].ResultInfo = `[${resultCode}]${resp.UserProfileItem[j].ResultInfo}`;
                                resp.ErrorInfo += `账号:${failCount},${resp.UserProfileItem[j].ResultInfo}\n`;
                                break;
                            }
                        }
                    }
                }
                if (resp.ActionStatus == ACTION_STATUS.FAIL) {
                    if (cbErr) cbErr(resp);
                } else if (cbOk) {
                    cbOk(resp);
                }
            },
            cbErr);
    };


    const proto_setProfilePortrait = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.PROFILE, 'portrait_set',
            {
                From_Account: ctx.identifier,
                ProfileItem: options.ProfileItem,
            },
            (resp) => {
                for (const i in options.ProfileItem) {
                    const profile = options.ProfileItem[i];
                    if (profile.Tag == 'Tag_Profile_IM_Nick') {
                        ctx.identifierNick = profile.Value;
                        break;
                    }
                }
                if (cbOk) cbOk(resp);
            }
            , cbErr);
    };


    const proto_addBlackList = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'black_list_add', {
            From_Account: ctx.identifier,
            To_Account: options.To_Account,
        },
            (resp) => {
                const newResp = convertErrorEn2ZhFriend(resp);
                if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
                    if (cbErr) cbErr(newResp);
                } else if (cbOk) {
                    cbOk(newResp);
                }
            }, cbErr);
    };


    const proto_deleteBlackList = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'black_list_delete', {
            From_Account: ctx.identifier,
            To_Account: options.To_Account,
        },
            (resp) => {
                const newResp = convertErrorEn2ZhFriend(resp);
                if (newResp.ActionStatus == ACTION_STATUS.FAIL) {
                    if (cbErr) cbErr(newResp);
                } else if (cbOk) {
                    cbOk(newResp);
                }
            }, cbErr);
    };


    const proto_getBlackList = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.FRIEND, 'black_list_get', {
            From_Account: ctx.identifier,
            StartIndex: options.StartIndex,
            MaxLimited: options.MaxLimited,
            LastSequence: options.LastSequence,
        },
            cbOk, cbErr);
    };


    const proto_getRecentContactList = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.RECENT_CONTACT, 'get', {
            From_Account: ctx.identifier,
            Count: options.Count,
        },
            cbOk, cbErr);
    };


    const proto_uploadPic = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        if (isAccessFormalEnv()) {
            cmdName = 'pic_up';
        } else {
            cmdName = 'pic_up_test';
        }
        ConnManager.apiCall(SRV_NAME.PIC, cmdName, {
            App_Version: VERSION_INFO.APP_VERSION,
            From_Account: ctx.identifier,
            To_Account: options.To_Account,
            Seq: options.Seq,
            Timestamp: options.Timestamp,
            Random: options.Random,
            File_Str_Md5: options.File_Str_Md5,
            File_Size: options.File_Size,
            File_Type: options.File_Type,
            Server_Ver: VERSION_INFO.SERVER_VERSION,
            Auth_Key: authkey,
            Busi_Id: options.Busi_Id,
            PkgFlag: options.PkgFlag,
            Slice_Offset: options.Slice_Offset,
            Slice_Size: options.Slice_Size,
            Slice_Data: options.Slice_Data,
        },
            cbOk, cbErr);
    };


    const proto_getIpAndAuthkey = function (cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.OPEN_IM, 'authkey', {}, cbOk, cbErr);
    };


    var proto_reportApiQuality = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.IM_OPEN_STAT, 'web_report', options, cbOk, cbErr);
    };


    const proto_getLongPollingId = function (options, cbOk, cbErr) {
        if (!checkLogin(cbErr, true)) return;
        ConnManager.apiCall(SRV_NAME.OPEN_IM, 'getlongpollingid', {},
            (resp) => {
                cbOk && cbOk(resp);
            }, cbErr);
    };


    var proto_applyDownload = function (options, cbOk, cbErr) {
        ConnManager.apiCall(SRV_NAME.PIC, 'apply_download', options, cbOk, cbErr);
    };


    initBrowserInfo();

    var ConnManager = lowerBR == false
        ? new function () {
            let onConnCallback = null;
            this.init = function (onConnNotify, cbOk, cbErr) {
                if (onConnNotify) onConnCallback = onConnNotify;
            };
            this.callBack = function (info) {
                if (onConnCallback) onConnCallback(info);
            };
            this.clear = function () {
                onConnCallback = null;
            };

            this.apiCall = function (type, cmd, data, cbOk, cbErr, timeout, isLongPolling) {
                const url = getApiUrl(type, cmd, cbOk, cbErr);
                if (url == false) return;

                ajaxRequestJson('POST', url, data, timeout, isLongPolling, (resp) => {
                    let errorCode = null,
                        tempErrorInfo = '';
                    if (cmd == 'pic_up') {
                        data.Slice_Data = '';
                    }
                    const info = `\n request url: \n${url}\n request body: \n${JSON.stringify(data)}\n response: \n${JSON.stringify(resp)}`;

                    if (resp.ActionStatus == ACTION_STATUS.OK) {
                        log.info(`[${type}][${cmd}]success: ${info}`);
                        if (cbOk) cbOk(resp);
                        errorCode = 0;
                        tempErrorInfo = '';
                    } else {
                        errorCode = resp.ErrorCode;
                        tempErrorInfo = resp.ErrorInfo;

                        if (cbErr) {
                            resp.SrcErrorInfo = resp.ErrorInfo;
                            resp.ErrorInfo = `[${type}][${cmd}]failed: ${info}`;
                            if (cmd != 'longpolling' || resp.ErrorCode != longPollingTimeOutErrorCode) {
                                log.error(resp.ErrorInfo);
                            }
                            cbErr(resp);
                        }
                    }
                    reportApiQuality(cmd, errorCode, tempErrorInfo);
                }, (err) => {
                    cbErr && cbErr(err);
                    reportApiQuality(cmd, err.ErrorCode, err.ErrorInfo);
                });
            };
        }()
        : new function () {
            let onConnCallback = null;
            this.init = function (onConnNotify, cbOk, cbErr) {
                if (onConnNotify) onConnCallback = onConnNotify;
            };
            this.callBack = function (info) {
                if (onConnCallback) onConnCallback(info);
            };
            this.clear = function () {
                onConnCallback = null;
            };

            this.apiCall = function (type, cmd, data, cbOk, cbErr, timeout, isLongPolling) {
                let url = getApiUrl(type, cmd, cbOk, cbErr);
                if (url == false) return;

                let reqId = jsonpRequestId++,
                    cbkey = 'jsonpcallback',
                    cbval = `jsonpRequest${reqId}`,
                    script = document.createElement('script'),
                    loaded = 0;

                window[cbval] = jsonpCallback;
                script.type = 'text/javascript';
                url = `${url}&${cbkey}=${cbval}&jsonpbody=${encodeURIComponent(JSON.stringify(data))}`;
                script.src = url;
                script.async = true;

                if (typeof script.onreadystatechange !== 'undefined') {
                    script.event = 'onclick';
                    script.htmlFor = script.id = `_jsonpRequest_${reqId}`;
                }

                script.onload = script.onreadystatechange = function () {
                    if ((this.readyState && this.readyState !== 'complete' && this.readyState !== 'loaded') || loaded) {
                        return false;
                    }
                    script.onload = script.onreadystatechange = null;
                    script.onclick && script.onclick();

                    const resp = jsonpLastRspData;
                    const info = `\n request url: \n${url}\n request body: \n${JSON.stringify(data)}\n response: \n${JSON.stringify(resp)}`;
                    if (resp.ActionStatus == ACTION_STATUS.OK) {
                        log.info(`[${type}][${cmd}]success: ${info}`);
                        cbOk && cbOk(resp);
                    } else {
                        resp.ErrorInfo = `[${type}][${cmd}]failed ${info}`;
                        if (cmd != 'longpolling' || resp.ErrorCode != longPollingTimeOutErrorCode) {
                            log.error(resp.ErrorInfo);
                        } else {
                            log.warn(`[${type}][${cmd}]success: ${info}`);
                        }
                        cbErr && cbErr(resp);
                    }
                    jsonpLastRspData = undefined;
                    document.body.removeChild(script);
                    loaded = 1;
                };


                document.body.appendChild(script);
            };
        }();

    var Session = function (type, id, name, icon, time, seq) {
        this._impl = {
            skey: Session.skey(type, id),
            type,
            id,
            name,
            icon,
            unread: 0,
            isAutoRead: false,
            time: time >= 0 ? time : 0,
            curMaxMsgSeq: seq >= 0 ? seq : 0,
            msgs: [],
            isFinished: 1,
        };
    };
    Session.skey = function (type, id) {
        return type + id;
    };
    Session.prototype.type = function () {
        return this._impl.type;
    };
    Session.prototype.id = function () {
        return this._impl.id;
    };
    Session.prototype.name = function () {
        return this._impl.name;
    };
    Session.prototype.icon = function () {
        return this._impl.icon;
    };
    Session.prototype.unread = function (val) {
        if (typeof val !== 'undefined') {
            this._impl.unread = val;
        } else {
            return this._impl.unread;
        }
    };
    Session.prototype.isFinished = function (val) {
        if (typeof val !== 'undefined') {
            this._impl.isFinished = val;
        } else {
            return this._impl.isFinished;
        }
    };
    Session.prototype.time = function () {
        return this._impl.time;
    };
    Session.prototype.curMaxMsgSeq = function (seq) {
        if (typeof seq !== 'undefined') {
            this._impl.curMaxMsgSeq = seq;
        } else {
            return this._impl.curMaxMsgSeq;
        }
    };
    Session.prototype.msgCount = function () {
        return this._impl.msgs.length;
    };
    Session.prototype.msg = function (index) {
        return this._impl.msgs[index];
    };
    Session.prototype.msgs = function () {
        return this._impl.msgs;
    };
    Session.prototype._impl_addMsg = function (msg) {
        this._impl.msgs.push(msg);

        if (msg.time > this._impl.time) { this._impl.time = msg.time; }

        if (msg.seq > this._impl.curMaxMsgSeq) { this._impl.curMaxMsgSeq = msg.seq; }

        if (!msg.isSend && !this._impl.isAutoRead) {
            this._impl.unread++;
        }
    };

    const C2CMsgReadedItem = function (toAccount, lastedMsgTime) {
        this.toAccount = toAccount;
        this.lastedMsgTime = lastedMsgTime;
    };

    const Msg = function (sess, isSend, seq, random, time, fromAccount, subType, fromAccountNick) {
        this.sess = sess;
        this.subType = subType >= 0 ? subType : 0;
        this.fromAccount = fromAccount;
        this.fromAccountNick = fromAccountNick || fromAccount;
        this.isSend = Boolean(isSend);
        this.seq = seq >= 0 ? seq : nextSeq();
        this.random = random >= 0 ? random : createRandom();
        this.time = time >= 0 ? time : unixtime();
        this.elems = [];
    };
    Msg.prototype.getSession = function () {
        return this.sess;
    };
    Msg.prototype.getType = function () {
        return this.subType;
    };
    Msg.prototype.getSubType = function () {
        return this.subType;
    };
    Msg.prototype.getFromAccount = function () {
        return this.fromAccount;
    };
    Msg.prototype.getFromAccountNick = function () {
        return this.fromAccountNick;
    };
    Msg.prototype.getIsSend = function () {
        return this.isSend;
    };
    Msg.prototype.getSeq = function () {
        return this.seq;
    };
    Msg.prototype.getTime = function () {
        return this.time;
    };
    Msg.prototype.getRandom = function () {
        return this.random;
    };
    Msg.prototype.getElems = function () {
        return this.elems;
    };

    Msg.prototype.addText = function (text) {
        this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.TEXT, text));
    };

    Msg.prototype.addFace = function (face) {
        this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.FACE, face));
    };

    Msg.prototype.addImage = function (image) {
        this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.IMAGE, image));
    };

    Msg.prototype.addLocation = function (location) {
        this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.LOCATION, location));
    };

    Msg.prototype.addFile = function (file) {
        this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.FILE, file));
    };

    Msg.prototype.addCustom = function (custom) {
        this.addElem(new webim.Msg.Elem(MSG_ELEMENT_TYPE.CUSTOM, custom));
    };
    Msg.prototype.addElem = function (elem) {
        this.elems.push(elem);
    };
    Msg.prototype.toHtml = function () {
        let html = '';
        for (const i in this.elems) {
            const elem = this.elems[i];
            html += elem.toHtml();
        }
        return html;
    };


    Msg.Elem = function (type, value) {
        this.type = type;
        this.content = value;
    };
    Msg.Elem.prototype.getType = function () {
        return this.type;
    };
    Msg.Elem.prototype.getContent = function () {
        return this.content;
    };
    Msg.Elem.prototype.toHtml = function () {
        let html;
        html = this.content.toHtml();
        return html;
    };


    Msg.Elem.Text = function (text) {
        this.text = tool.xssFilter(text);
    };
    Msg.Elem.Text.prototype.getText = function () {
        return this.text;
    };
    Msg.Elem.Text.prototype.toHtml = function () {
        return this.text;
    };


    Msg.Elem.Face = function (index, data) {
        this.index = index;
        this.data = data;
    };
    Msg.Elem.Face.prototype.getIndex = function () {
        return this.index;
    };
    Msg.Elem.Face.prototype.getData = function () {
        return this.data;
    };
    Msg.Elem.Face.prototype.toHtml = function () {
        let faceUrl = null;
        const index = emotionDataIndexs[this.data];
        const emotion = emotions[index];
        if (emotion && emotion[1]) {
            faceUrl = emotion[1];
        }
        if (faceUrl) {
            return `<img src='${faceUrl}'/>`;
        }
        return this.data;
    };


    Msg.Elem.Location = function (longitude, latitude, desc) {
        this.latitude = latitude;
        this.longitude = longitude;
        this.desc = desc;
    };
    Msg.Elem.Location.prototype.getLatitude = function () {
        return this.latitude;
    };
    Msg.Elem.Location.prototype.getLongitude = function () {
        return this.longitude;
    };
    Msg.Elem.Location.prototype.getDesc = function () {
        return this.desc;
    };
    Msg.Elem.Location.prototype.toHtml = function () {
        return `经度=${this.longitude},纬度=${this.latitude},描述=${this.desc}`;
    };


    Msg.Elem.Images = function (imageId) {
        this.UUID = imageId;
        this.ImageInfoArray = [];
    };
    Msg.Elem.Images.prototype.addImage = function (image) {
        this.ImageInfoArray.push(image);
    };
    Msg.Elem.Images.prototype.toHtml = function () {
        const smallImage = this.getImage(IMAGE_TYPE.SMALL);
        let bigImage = this.getImage(IMAGE_TYPE.LARGE);
        let oriImage = this.getImage(IMAGE_TYPE.ORIGIN);
        if (!bigImage) {
            bigImage = smallImage;
        }
        if (!oriImage) {
            oriImage = smallImage;
        }
        return `<img src='${smallImage.getUrl()}#${bigImage.getUrl()}#${oriImage.getUrl()}' style='CURSOR: hand' id='${this.getImageId()}' bigImgUrl='${bigImage.getUrl()}' onclick='imageClick(this)' />`;
    };
    Msg.Elem.Images.prototype.getImageId = function () {
        return this.UUID;
    };
    Msg.Elem.Images.prototype.getImage = function (type) {
        for (const i in this.ImageInfoArray) {
            if (this.ImageInfoArray[i].getType() == type) {
                return this.ImageInfoArray[i];
            }
        }
        return null;
    };

    Msg.Elem.Images.Image = function (type, size, width, height, url) {
        this.type = type;
        this.size = size;
        this.width = width;
        this.height = height;
        this.url = url;
    };
    Msg.Elem.Images.Image.prototype.getType = function () {
        return this.type;
    };
    Msg.Elem.Images.Image.prototype.getSize = function () {
        return this.size;
    };
    Msg.Elem.Images.Image.prototype.getWidth = function () {
        return this.width;
    };
    Msg.Elem.Images.Image.prototype.getHeight = function () {
        return this.height;
    };
    Msg.Elem.Images.Image.prototype.getUrl = function () {
        return this.url;
    };


    Msg.Elem.Sound = function (uuid, second, size, senderId, receiverId, downFlag, chatType) {
        this.uuid = uuid;
        this.second = second;
        this.size = size;
        this.senderId = senderId;
        this.receiverId = receiverId;
        this.downFlag = downFlag;
        this.busiId = chatType == SESSION_TYPE.C2C ? 2 : 1;


        if (downFlag !== undefined && busiId !== undefined) {
            getFileDownUrlV2(uuid, senderId, second, downFlag, receiverId, this.busiId, UPLOAD_RES_TYPE.SOUND);
        } else {
            this.downUrl = getSoundDownUrl(uuid, senderId, second);
        }
    };
    Msg.Elem.Sound.prototype.getUUID = function () {
        return this.uuid;
    };
    Msg.Elem.Sound.prototype.getSecond = function () {
        return this.second;
    };
    Msg.Elem.Sound.prototype.getSize = function () {
        return this.size;
    };
    Msg.Elem.Sound.prototype.getSenderId = function () {
        return this.senderId;
    };
    Msg.Elem.Sound.prototype.getDownUrl = function () {
        return this.downUrl;
    };
    Msg.Elem.Sound.prototype.toHtml = function () {
        if (BROWSER_INFO.type == 'ie' && parseInt(BROWSER_INFO.ver) <= 8) {
            return `[这是一条语音消息]demo暂不支持ie8(含)以下浏览器播放语音,语音URL:${this.downUrl}`;
        }
        return `<audio id="uuid_${this.uuid}" src="${this.downUrl}" controls="controls" onplay="onChangePlayAudio(this)" preload="none"></audio>`;
    };


    Msg.Elem.File = function (uuid, name, size, senderId, receiverId, downFlag, chatType) {
        this.uuid = uuid;
        this.name = name;
        this.size = size;
        this.senderId = senderId;
        this.receiverId = receiverId;
        this.downFlag = downFlag;

        this.busiId = chatType == SESSION_TYPE.C2C ? 2 : 1;


        if (downFlag !== undefined && busiId !== undefined) {
            getFileDownUrlV2(uuid, senderId, name, downFlag, receiverId, this.busiId, UPLOAD_RES_TYPE.FILE);
        } else {
            this.downUrl = getFileDownUrl(uuid, senderId, name);
        }
    };
    Msg.Elem.File.prototype.getUUID = function () {
        return this.uuid;
    };
    Msg.Elem.File.prototype.getName = function () {
        return this.name;
    };
    Msg.Elem.File.prototype.getSize = function () {
        return this.size;
    };
    Msg.Elem.File.prototype.getSenderId = function () {
        return this.senderId;
    };
    Msg.Elem.File.prototype.getDownUrl = function () {
        return this.downUrl;
    };
    Msg.Elem.File.prototype.getDownFlag = function () {
        return this.downFlag;
    };
    Msg.Elem.File.prototype.toHtml = function () {
        let fileSize,
            unitStr;
        fileSize = this.size;
        unitStr = 'Byte';
        if (this.size >= 1024) {
            fileSize = Math.round(this.size / 1024);
            unitStr = 'KB';
        }
        return `<a href="javascript" onclick="webim.onDownFile("${this.uuid}")" title="点击下载文件" ><i class="glyphicon glyphicon-file">&nbsp;${this.name}(${fileSize}${unitStr})</i></a>`;
    };


    Msg.Elem.GroupTip = function (opType, opUserId, groupId, groupName, userIdList) {
        this.opType = opType;
        this.opUserId = opUserId;
        this.groupId = groupId;
        this.groupName = groupName;
        this.userIdList = userIdList || [];
        this.groupInfoList = [];
        this.memberInfoList = [];
        this.groupMemberNum = null;
    };
    Msg.Elem.GroupTip.prototype.addGroupInfo = function (groupInfo) {
        this.groupInfoList.push(groupInfo);
    };
    Msg.Elem.GroupTip.prototype.addMemberInfo = function (memberInfo) {
        this.memberInfoList.push(memberInfo);
    };
    Msg.Elem.GroupTip.prototype.getOpType = function () {
        return this.opType;
    };
    Msg.Elem.GroupTip.prototype.getOpUserId = function () {
        return this.opUserId;
    };
    Msg.Elem.GroupTip.prototype.getGroupId = function () {
        return this.groupId;
    };
    Msg.Elem.GroupTip.prototype.getGroupName = function () {
        return this.groupName;
    };
    Msg.Elem.GroupTip.prototype.getUserIdList = function () {
        return this.userIdList;
    };
    Msg.Elem.GroupTip.prototype.getGroupInfoList = function () {
        return this.groupInfoList;
    };
    Msg.Elem.GroupTip.prototype.getMemberInfoList = function () {
        return this.memberInfoList;
    };
    Msg.Elem.GroupTip.prototype.getGroupMemberNum = function () {
        return this.groupMemberNum;
    };
    Msg.Elem.GroupTip.prototype.setGroupMemberNum = function (groupMemberNum) {
        return this.groupMemberNum = groupMemberNum;
    };
    Msg.Elem.GroupTip.prototype.toHtml = function () {
        let text = '[群提示消息]';
        const maxIndex = GROUP_TIP_MAX_USER_COUNT - 1;
        switch (this.opType) {
            case GROUP_TIP_TYPE.JOIN:
                text += `${this.opUserId}邀请了`;
                for (var m in this.userIdList) {
                    text += `${this.userIdList[m]},`;
                    if (this.userIdList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
                        text += `等${this.userIdList.length}人`;
                        break;
                    }
                }
                text += '加入该群';
                break;
            case GROUP_TIP_TYPE.QUIT:
                text += `${this.opUserId}主动退出该群`;
                break;
            case GROUP_TIP_TYPE.KICK:
                text += `${this.opUserId}将`;
                for (var m in this.userIdList) {
                    text += `${this.userIdList[m]},`;
                    if (this.userIdList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
                        text += `等${this.userIdList.length}人`;
                        break;
                    }
                }
                text += '踢出该群';
                break;
            case GROUP_TIP_TYPE.SET_ADMIN:
                text += `${this.opUserId}将`;
                for (var m in this.userIdList) {
                    text += `${this.userIdList[m]},`;
                    if (this.userIdList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
                        text += `等${this.userIdList.length}人`;
                        break;
                    }
                }
                text += '设为管理员';
                break;
            case GROUP_TIP_TYPE.CANCEL_ADMIN:
                text += `${this.opUserId}取消`;
                for (var m in this.userIdList) {
                    text += `${this.userIdList[m]},`;
                    if (this.userIdList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
                        text += `等${this.userIdList.length}人`;
                        break;
                    }
                }
                text += '的管理员资格';
                break;


            case GROUP_TIP_TYPE.MODIFY_GROUP_INFO:
                text += `${this.opUserId}修改了群资料：`;
                for (var m in this.groupInfoList) {
                    const type = this.groupInfoList[m].getType();
                    const value = this.groupInfoList[m].getValue();
                    switch (type) {
                        case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.FACE_URL:
                            text += `群头像为${value}; `;
                            break;
                        case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.NAME:
                            text += `群名称为${value}; `;
                            break;
                        case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.OWNER:
                            text += `群主为${value}; `;
                            break;
                        case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.NOTIFICATION:
                            text += `群公告为${value}; `;
                            break;
                        case GROUP_TIP_MODIFY_GROUP_INFO_TYPE.INTRODUCTION:
                            text += `群简介为${value}; `;
                            break;
                        default:
                            text += `未知信息为:type=${type},value=${value}; `;
                            break;
                    }
                }
                break;

            case GROUP_TIP_TYPE.MODIFY_MEMBER_INFO:
                text += `${this.opUserId}修改了群成员资料:`;
                for (var m in this.memberInfoList) {
                    const userId = this.memberInfoList[m].getUserId();
                    const shutupTime = this.memberInfoList[m].getShutupTime();
                    text += `${userId}: `;
                    if (shutupTime != null && shutupTime !== undefined) {
                        if (shutupTime == 0) {
                            text += '取消禁言; ';
                        } else {
                            text += `禁言${shutupTime}秒; `;
                        }
                    } else {
                        text += ' shutupTime为空';
                    }
                    if (this.memberInfoList.length > GROUP_TIP_MAX_USER_COUNT && m == maxIndex) {
                        text += `等${this.memberInfoList.length}人`;
                        break;
                    }
                }
                break;

            case GROUP_TIP_TYPE.READED:

                Log.info('消息已读同步');
                break;
            default:
                text += `未知群提示消息类型：type=${this.opType}`;
                break;
        }
        return text;
    };


    Msg.Elem.GroupTip.GroupInfo = function (type, value) {
        this.type = type;
        this.value = value;
    };
    Msg.Elem.GroupTip.GroupInfo.prototype.getType = function () {
        return this.type;
    };
    Msg.Elem.GroupTip.GroupInfo.prototype.getValue = function () {
        return this.value;
    };


    Msg.Elem.GroupTip.MemberInfo = function (userId, shutupTime) {
        this.userId = userId;
        this.shutupTime = shutupTime;
    };
    Msg.Elem.GroupTip.MemberInfo.prototype.getUserId = function () {
        return this.userId;
    };
    Msg.Elem.GroupTip.MemberInfo.prototype.getShutupTime = function () {
        return this.shutupTime;
    };


    Msg.Elem.Custom = function (data, desc, ext) {
        this.data = data;
        this.desc = desc;
        this.ext = ext;
    };
    Msg.Elem.Custom.prototype.getData = function () {
        return this.data;
    };
    Msg.Elem.Custom.prototype.getDesc = function () {
        return this.desc;
    };
    Msg.Elem.Custom.prototype.getExt = function () {
        return this.ext;
    };
    Msg.Elem.Custom.prototype.toHtml = function () {
        return this.data;
    };


    var MsgStore = new function () {
        const sessMap = {};
        let sessTimeline = [];
        msgCache = {};

        this.cookie = '';
        this.syncFlag = 0;

        const visitSess = function (visitor) {
            for (const i in sessMap) {
                visitor(sessMap[i]);
            }
        };


        const checkDupMsg = function (msg) {
            let dup = false;
            const first_key = msg.sess._impl.skey;
            const second_key = msg.isSend + msg.seq + msg.random;
            const tempMsg = msgCache[first_key] && msgCache[first_key][second_key];
            if (tempMsg) {
                dup = true;
            }
            if (msgCache[first_key]) {
                msgCache[first_key][second_key] = { time: msg.time };
            } else {
                msgCache[first_key] = {};
                msgCache[first_key][second_key] = { time: msg.time };
            }
            return dup;
        };

        this.sessMap = function () {
            return sessMap;
        };
        this.sessCount = function () {
            return sessTimeline.length;
        };
        this.sessByTypeId = function (type, id) {
            const skey = Session.skey(type, id);
            if (skey === undefined || skey == null) return null;
            return sessMap[skey];
        };
        this.delSessByTypeId = function (type, id) {
            const skey = Session.skey(type, id);
            if (skey === undefined || skey == null) return false;
            if (sessMap[skey]) {
                delete sessMap[skey];
                delete msgCache[skey];
            }
            return true;
        };
        this.resetCookieAndSyncFlag = function () {
            this.cookie = '';
            this.syncFlag = 0;
        };


        this.setAutoRead = function (selSess, isOn, isResetAll) {
            if (isResetAll) {
                visitSess((s) => {
                    s._impl.isAutoRead = false;
                });
            }
            if (selSess) {
                selSess._impl.isAutoRead = isOn;
                if (isOn) {
                    selSess._impl.unread = 0;

                    if (selSess._impl.type == SESSION_TYPE.C2C) {
                        const tmpC2CMsgReadedItem = [];
                        tmpC2CMsgReadedItem.push(new C2CMsgReadedItem(selSess._impl.id, selSess._impl.time));

                        proto_c2CMsgReaded(MsgStore.cookie,
                            tmpC2CMsgReadedItem,
                            (resp) => {
                                log.info('[setAutoRead]: c2CMsgReaded success');
                            },
                            (err) => {
                                log.error(`[setAutoRead}: c2CMsgReaded failed:${err.ErrorInfo}`);
                            });
                    } else if (selSess._impl.type == SESSION_TYPE.GROUP) {
                        const tmpOpt = {
                            GroupId: selSess._impl.id,
                            MsgReadedSeq: selSess._impl.curMaxMsgSeq,
                        };

                        proto_groupMsgReaded(tmpOpt,
                            (resp) => {
                                log.info('groupMsgReaded success');
                            },
                            (err) => {
                                log.error(`groupMsgReaded failed:${err.ErrorInfo}`);
                            });
                    }
                }
            }
        };

        this.c2CMsgReaded = function (opts, cbOk, cbErr) {
            const tmpC2CMsgReadedItem = [];
            tmpC2CMsgReadedItem.push(new C2CMsgReadedItem(opts.To_Account, opts.LastedMsgTime));

            proto_c2CMsgReaded(MsgStore.cookie,
                tmpC2CMsgReadedItem,
                (resp) => {
                    if (cbOk) {
                        log.info('c2CMsgReaded success');
                        cbOk(resp);
                    }
                },
                (err) => {
                    if (cbErr) {
                        log.error(`c2CMsgReaded failed:${err.ErrorInfo}`);
                        cbErr(err);
                    }
                });
        };

        this.addSession = function (sess) {
            sessMap[sess._impl.skey] = sess;
        };
        this.delSession = function (sess) {
            delete sessMap[sess._impl.skey];
        };
        this.addMsg = function (msg) {
            if (checkDupMsg(msg)) return false;
            const sess = msg.sess;
            if (!sessMap[sess._impl.skey]) this.addSession(sess);
            sess._impl_addMsg(msg);
            return true;
        };
        this.updateTimeline = function () {
            const arr = new Array();
            visitSess((sess) => {
                arr.push(sess);
            });
            arr.sort((a, b) => b.time - a.time);
            sessTimeline = arr;
        };
    }();

    var MsgManager = new function () {
        let onMsgCallback = null;

        let onGroupInfoChangeCallback = null;

        let onGroupSystemNotifyCallbacks = {
            1: null,
            2: null,
            3: null,
            4: null,
            5: null,
            6: null,
            7: null,
            8: null,
            9: null,
            10: null,
            11: null,
            15: null,
            255: null,
        };

        let onFriendSystemNotifyCallbacks = {
            1: null,
            2: null,
            3: null,
            4: null,
            5: null,
            6: null,
            7: null,
            8: null,
        };

        let onProfileSystemNotifyCallbacks = {
            1: null,
        };

        const onMsgReadCallback = null;


        let longPollingOn = false;
        const isLongPollingRequesting = false;
        let notifySeq = 0;
        let noticeSeq = 0;


        let onBigGroupMsgCallback = null;
        let bigGroupLongPollingOn = false;
        let bigGroupLongPollingStartSeq = 0;
        let bigGroupLongPollingHoldTime = 90;
        let bigGroupLongPollingKey = null;
        let bigGroupLongPollingMsgMap = {};


        let getLostGroupMsgCount = 0;

        const myGroupMaxSeqs = {};

        let groupSystemMsgsCache = {};


        this.setLongPollingOn = function (isOn) {
            longPollingOn = isOn;
        };
        this.getLongPollingOn = function () {
            return longPollingOn;
        };


        this.resetLongPollingInfo = function () {
            longPollingOn = false;
            notifySeq = 0;
            noticeSeq = 0;
        };


        this.setBigGroupLongPollingOn = function (isOn) {
            bigGroupLongPollingOn = isOn;
        };

        this.setBigGroupLongPollingKey = function (key) {
            bigGroupLongPollingKey = key;
        };

        this.resetBigGroupLongPollingInfo = function () {
            bigGroupLongPollingOn = false;
            bigGroupLongPollingStartSeq = 0;
            bigGroupLongPollingKey = null;
            bigGroupLongPollingMsgMap = {};
        };


        this.setBigGroupLongPollingMsgMap = function (groupId, count) {
            let bigGroupLongPollingMsgCount = bigGroupLongPollingMsgMap[groupId];
            if (bigGroupLongPollingMsgCount) {
                bigGroupLongPollingMsgCount = parseInt(bigGroupLongPollingMsgCount) + count;
                bigGroupLongPollingMsgMap[groupId] = bigGroupLongPollingMsgCount;
            } else {
                bigGroupLongPollingMsgMap[groupId] = count;
            }
        };


        this.clear = function () {
            onGroupInfoChangeCallback = null;
            onGroupSystemNotifyCallbacks = {
                1: null,
                2: null,
                3: null,
                4: null,
                5: null,
                6: null,
                7: null,
                8: null,
                9: null,
                10: null,
                11: null,
                15: null,
                255: null,
            };
            onFriendSystemNotifyCallbacks = {
                1: null,
                2: null,
                3: null,
                4: null,
                5: null,
                6: null,
                7: null,
                8: null,
            };
            onProfileSystemNotifyCallbacks = {
                1: null,
            };

            onMsgCallback = null;
            longPollingOn = false;
            notifySeq = 0;
            noticeSeq = 0;


            onBigGroupMsgCallback = null;
            bigGroupLongPollingOn = false;
            bigGroupLongPollingStartSeq = 0;
            bigGroupLongPollingKey = null;
            bigGroupLongPollingMsgMap = {};

            groupSystemMsgsCache = {};

            ipList = [];
            authkey = null;
            expireTime = null;
        };


        const initIpAndAuthkey = function (cbOk, cbErr) {
            proto_getIpAndAuthkey((resp) => {
                ipList = resp.IpList;
                authkey = resp.AuthKey;
                expireTime = resp.ExpireTime;
                if (cbOk) cbOk(resp);
            },
                (err) => {
                    log.error(`initIpAndAuthkey failed:${err.ErrorInfo}`);
                    if (cbErr) cbErr(err);
                },
            );
        };


        const initMyGroupMaxSeqs = function (cbOk, cbErr) {
            const opts = {
                Member_Account: ctx.identifier,
                Limit: 1000,
                Offset: 0,
                GroupBaseInfoFilter: [
                    'NextMsgSeq',
                ],
            };
            proto_getJoinedGroupListHigh(opts, (resp) => {
                if (!resp.GroupIdList || resp.GroupIdList.length == 0) {
                    log.info('initMyGroupMaxSeqs: 目前还没有加入任何群组');
                    if (cbOk) cbOk(resp);
                    return;
                }
                for (let i = 0; i < resp.GroupIdList.length; i++) {
                    const group_id = resp.GroupIdList[i].GroupId;
                    const curMaxSeq = resp.GroupIdList[i].NextMsgSeq - 1;
                    myGroupMaxSeqs[group_id] = curMaxSeq;
                }

                if (cbOk) cbOk(resp);
            },
                (err) => {
                    log.error(`initMyGroupMaxSeqs failed:${err.ErrorInfo}`);
                    if (cbErr) cbErr(err);
                },
            );
        };


        const getLostGroupMsgs = function (groupId, reqMsgSeq, reqMsgNumber) {
            getLostGroupMsgCount++;

            const tempOpts = {
                GroupId: groupId,
                ReqMsgSeq: reqMsgSeq,
                ReqMsgNumber: reqMsgNumber,
            };

            log.warn(`第${getLostGroupMsgCount}次补齐群消息,参数=${JSON.stringify(tempOpts)}`);
            MsgManager.syncGroupMsgs(tempOpts);
        };


        const updateMyGroupCurMaxSeq = function (groupId, msgSeq) {
            const curMsgSeq = myGroupMaxSeqs[groupId];
            if (curMsgSeq) {
                if (msgSeq > curMsgSeq) {
                    myGroupMaxSeqs[groupId] = msgSeq;
                }
            } else {
                myGroupMaxSeqs[groupId] = msgSeq;
            }
        };


        const addGroupMsgList = function (msgs, new_group_msgs) {
            for (const p in msgs) {
                const newGroupMsg = msgs[p];


                if (newGroupMsg.From_Account) {
                    const msg = handlerGroupMsg(newGroupMsg, false, true);
                    if (msg) {
                        new_group_msgs.push(msg);
                    }

                    updateMyGroupCurMaxSeq(newGroupMsg.ToGroupId, newGroupMsg.MsgSeq);
                }
            }
            return new_group_msgs;
        };


        var handlerOrdinaryAndTipC2cMsgs = function (eventType, groupMsgArray) {
            const groupMsgMap = {};
            let new_group_msgs = [];
            const minGroupMsgSeq = 99999999;
            const maxGroupMsgSeq = -1;
            for (const j in groupMsgArray) {
                let groupMsgs = groupMsgMap[groupMsgArray[j].ToGroupId];
                if (!groupMsgs) {
                    groupMsgs = groupMsgMap[groupMsgArray[j].ToGroupId] = {
                        min: minGroupMsgSeq,
                        max: maxGroupMsgSeq,
                        msgs: [],
                    };
                }

                if (groupMsgArray[j].NoticeSeq > noticeSeq) {
                    log.warn(`noticeSeq=${noticeSeq},msgNoticeSeq=${groupMsgArray[j].NoticeSeq}`);
                    noticeSeq = groupMsgArray[j].NoticeSeq;
                }
                groupMsgArray[j].Event = eventType;
                groupMsgMap[groupMsgArray[j].ToGroupId].msgs.push(groupMsgArray[j]);
                if (groupMsgArray[j].MsgSeq < groupMsgs.min) {
                    groupMsgMap[groupMsgArray[j].ToGroupId].min = groupMsgArray[j].MsgSeq;
                }
                if (groupMsgArray[j].MsgSeq > groupMsgs.max) {
                    groupMsgMap[groupMsgArray[j].ToGroupId].max = groupMsgArray[j].MsgSeq;
                }
            }

            for (const groupId in groupMsgMap) {
                const tempCount = groupMsgMap[groupId].max - groupMsgMap[groupId].min + 1;
                const curMaxMsgSeq = myGroupMaxSeqs[groupId];
                if (curMaxMsgSeq) {
                    if (groupMsgMap[groupId].min - curMaxMsgSeq > 1 || groupMsgMap[groupId].msgs.length < tempCount) {
                        log.warn(`发起一次补齐群消息请求,curMaxMsgSeq=${curMaxMsgSeq}, minMsgSeq=${groupMsgMap[groupId].min}, maxMsgSeq=${groupMsgMap[groupId].max}, msgs.length=${groupMsgMap[groupId].msgs.length}, tempCount=${tempCount}`);
                        getLostGroupMsgs(groupId, groupMsgMap[groupId].max, groupMsgMap[groupId].max - curMaxMsgSeq);

                        updateMyGroupCurMaxSeq(groupId, groupMsgMap[groupId].max);
                    } else {
                        new_group_msgs = addGroupMsgList(groupMsgMap[groupId].msgs, new_group_msgs);
                    }
                } else {
                    log.warn(`不存在该群的最大消息seq，群id=${groupId}`);


                    if (groupMsgMap[groupId].msgs.length < tempCount) {
                        log.warn(`发起一次补齐群消息请求,minMsgSeq=${groupMsgMap[groupId].min}, maxMsgSeq=${groupMsgMap[groupId].max}, msgs.length=${groupMsgMap[groupId].msgs.length}, tempCount=${tempCount}`);
                        getLostGroupMsgs(groupId, groupMsgMap[groupId].max, tempCount);

                        updateMyGroupCurMaxSeq(groupId, groupMsgMap[groupId].max);
                    } else {
                        new_group_msgs = addGroupMsgList(groupMsgMap[groupId].msgs, new_group_msgs);
                    }
                }
            }
            if (new_group_msgs.length) {
                MsgStore.updateTimeline();
            }
            if (onMsgCallback && new_group_msgs.length) onMsgCallback(new_group_msgs);
        };


        const handlerOrdinaryAndTipGroupMsgs = function (eventType, groupMsgArray) {
            const groupMsgMap = {};
            let new_group_msgs = [];
            const minGroupMsgSeq = 99999999;
            const maxGroupMsgSeq = -1;
            for (const j in groupMsgArray) {
                let groupMsgs = groupMsgMap[groupMsgArray[j].ToGroupId];
                if (!groupMsgs) {
                    groupMsgs = groupMsgMap[groupMsgArray[j].ToGroupId] = {
                        min: minGroupMsgSeq,
                        max: maxGroupMsgSeq,
                        msgs: [],
                    };
                }

                if (groupMsgArray[j].NoticeSeq > noticeSeq) {
                    log.warn(`noticeSeq=${noticeSeq},msgNoticeSeq=${groupMsgArray[j].NoticeSeq}`);
                    noticeSeq = groupMsgArray[j].NoticeSeq;
                }
                groupMsgArray[j].Event = eventType;
                groupMsgMap[groupMsgArray[j].ToGroupId].msgs.push(groupMsgArray[j]);
                if (groupMsgArray[j].MsgSeq < groupMsgs.min) {
                    groupMsgMap[groupMsgArray[j].ToGroupId].min = groupMsgArray[j].MsgSeq;
                }
                if (groupMsgArray[j].MsgSeq > groupMsgs.max) {
                    groupMsgMap[groupMsgArray[j].ToGroupId].max = groupMsgArray[j].MsgSeq;
                }
            }

            for (const groupId in groupMsgMap) {
                const tempCount = groupMsgMap[groupId].max - groupMsgMap[groupId].min + 1;
                const curMaxMsgSeq = myGroupMaxSeqs[groupId];
                if (curMaxMsgSeq) {
                    if (groupMsgMap[groupId].min - curMaxMsgSeq > 1 || groupMsgMap[groupId].msgs.length < tempCount) {
                        log.warn(`发起一次补齐群消息请求,curMaxMsgSeq=${curMaxMsgSeq}, minMsgSeq=${groupMsgMap[groupId].min}, maxMsgSeq=${groupMsgMap[groupId].max}, msgs.length=${groupMsgMap[groupId].msgs.length}, tempCount=${tempCount}`);
                        getLostGroupMsgs(groupId, groupMsgMap[groupId].max, groupMsgMap[groupId].max - curMaxMsgSeq);

                        updateMyGroupCurMaxSeq(groupId, groupMsgMap[groupId].max);
                    } else {
                        new_group_msgs = addGroupMsgList(groupMsgMap[groupId].msgs, new_group_msgs);
                    }
                } else {
                    log.warn(`不存在该群的最大消息seq，群id=${groupId}`);


                    if (groupMsgMap[groupId].msgs.length < tempCount) {
                        log.warn(`发起一次补齐群消息请求,minMsgSeq=${groupMsgMap[groupId].min}, maxMsgSeq=${groupMsgMap[groupId].max}, msgs.length=${groupMsgMap[groupId].msgs.length}, tempCount=${tempCount}`);
                        getLostGroupMsgs(groupId, groupMsgMap[groupId].max, tempCount);

                        updateMyGroupCurMaxSeq(groupId, groupMsgMap[groupId].max);
                    } else {
                        new_group_msgs = addGroupMsgList(groupMsgMap[groupId].msgs, new_group_msgs);
                    }
                }
            }
            if (new_group_msgs.length) {
                MsgStore.updateTimeline();
            }
            if (onMsgCallback && new_group_msgs.length) onMsgCallback(new_group_msgs);
        };


        const handlerGroupTips = function (groupTips) {
            const new_group_msgs = [];
            for (const o in groupTips) {
                const groupTip = groupTips[o];

                groupTip.Event = LONG_POLLINNG_EVENT_TYPE.GROUP_TIP;

                if (groupTip.NoticeSeq > noticeSeq) {
                    noticeSeq = groupTip.NoticeSeq;
                }
                const msg = handlerGroupMsg(groupTip, false, true);
                if (msg) {
                    new_group_msgs.push(msg);
                }
            }
            if (new_group_msgs.length) {
                MsgStore.updateTimeline();
            }
            if (onMsgCallback && new_group_msgs.length) onMsgCallback(new_group_msgs);
        };


        const handlerGroupSystemMsgs = function (groupSystemMsgs, isNeedValidRepeatMsg) {
            for (const k in groupSystemMsgs) {
                const groupTip = groupSystemMsgs[k];
                const groupReportTypeMsg = groupTip.MsgBody;
                const reportType = groupReportTypeMsg.ReportType;

                if (isNeedValidRepeatMsg == false && groupTip.NoticeSeq && groupTip.NoticeSeq > noticeSeq) {
                    noticeSeq = groupTip.NoticeSeq;
                }
                const toAccount = groupTip.GroupInfo.To_Account;


                if (isNeedValidRepeatMsg) {
                    const key = `${groupTip.ToGroupId}_${reportType}_${groupReportTypeMsg.Operator_Account}`;
                    const isExist = groupSystemMsgsCache[key];
                    if (isExist) {
                        log.warn(`收到重复的群系统消息：key=${key}`);
                        continue;
                    }
                    groupSystemMsgsCache[key] = true;
                }

                const notify = {
                    SrcFlag: 0,
                    ReportType: reportType,
                    GroupId: groupTip.ToGroupId,
                    GroupName: groupTip.GroupInfo.GroupName,
                    Operator_Account: groupReportTypeMsg.Operator_Account,
                    MsgTime: groupTip.MsgTimeStamp,
                    groupReportTypeMsg,
                };
                switch (reportType) {
                    case GROUP_SYSTEM_TYPE.JOIN_GROUP_REQUEST:
                        notify.RemarkInfo = groupReportTypeMsg.RemarkInfo;
                        notify.MsgKey = groupReportTypeMsg.MsgKey;
                        notify.Authentication = groupReportTypeMsg.Authentication;
                        notify.UserDefinedField = groupTip.UserDefinedField;
                        notify.From_Account = groupTip.From_Account;
                        notify.MsgSeq = groupTip.ClientSeq;
                        notify.MsgRandom = groupTip.MsgRandom;
                        break;
                    case GROUP_SYSTEM_TYPE.JOIN_GROUP_ACCEPT:
                    case GROUP_SYSTEM_TYPE.JOIN_GROUP_REFUSE:
                        notify.RemarkInfo = groupReportTypeMsg.RemarkInfo;
                        break;
                    case GROUP_SYSTEM_TYPE.KICK:
                    case GROUP_SYSTEM_TYPE.DESTORY:
                    case GROUP_SYSTEM_TYPE.CREATE:
                    case GROUP_SYSTEM_TYPE.INVITED_JOIN_GROUP_REQUEST:
                    case GROUP_SYSTEM_TYPE.QUIT:
                    case GROUP_SYSTEM_TYPE.SET_ADMIN:
                    case GROUP_SYSTEM_TYPE.CANCEL_ADMIN:
                    case GROUP_SYSTEM_TYPE.REVOKE:
                        break;
                    case GROUP_SYSTEM_TYPE.READED:


                        break;
                    case GROUP_SYSTEM_TYPE.CUSTOM:
                        notify.MsgSeq = groupTip.MsgSeq;
                        notify.UserDefinedField = groupReportTypeMsg.UserDefinedField;
                        break;
                    default:
                        log.error(`未知群系统消息类型：reportType=${reportType}`);
                        break;
                }

                if (isNeedValidRepeatMsg) {
                    if (reportType == GROUP_SYSTEM_TYPE.JOIN_GROUP_REQUEST) {
                        if (onGroupSystemNotifyCallbacks[reportType]) onGroupSystemNotifyCallbacks[reportType](notify);
                    }
                } else if (onGroupSystemNotifyCallbacks[reportType]) onGroupSystemNotifyCallbacks[reportType](notify);
            }
        };


        const handlerFriendSystemNotices = function (friendSystemNotices, isNeedValidRepeatMsg) {
            let friendNotice,
                type,
                notify;
            for (const k in friendSystemNotices) {
                friendNotice = friendSystemNotices[k];
                type = friendNotice.PushType;

                if (isNeedValidRepeatMsg == false && friendNotice.NoticeSeq && friendNotice.NoticeSeq > noticeSeq) {
                    noticeSeq = friendNotice.NoticeSeq;
                }
                notify = { Type: type };
                switch (type) {
                    case FRIEND_NOTICE_TYPE.FRIEND_ADD:
                        notify.Accounts = friendNotice.FriendAdd_Account;
                        break;
                    case FRIEND_NOTICE_TYPE.FRIEND_DELETE:
                        notify.Accounts = friendNotice.FriendDel_Account;
                        break;
                    case FRIEND_NOTICE_TYPE.PENDENCY_ADD:
                        notify.PendencyList = friendNotice.PendencyAdd;
                        break;
                    case FRIEND_NOTICE_TYPE.PENDENCY_DELETE:
                        notify.Accounts = friendNotice.FrienPencydDel_Account;
                        break;
                    case FRIEND_NOTICE_TYPE.BLACK_LIST_ADD:
                        notify.Accounts = friendNotice.BlackListAdd_Account;
                        break;
                    case FRIEND_NOTICE_TYPE.BLACK_LIST_DELETE:
                        notify.Accounts = friendNotice.BlackListDel_Account;
                        break;

                    default:
                        log.error(`未知好友系统通知类型：friendNotice=${JSON.stringify(friendNotice)}`);
                        break;
                }

                if (isNeedValidRepeatMsg) {
                    if (type == FRIEND_NOTICE_TYPE.PENDENCY_ADD) {
                        if (onFriendSystemNotifyCallbacks[type]) onFriendSystemNotifyCallbacks[type](notify);
                    }
                } else if (onFriendSystemNotifyCallbacks[type]) onFriendSystemNotifyCallbacks[type](notify);
            }
        };


        const handlerProfileSystemNotices = function (profileSystemNotices, isNeedValidRepeatMsg) {
            let profileNotice,
                type,
                notify;
            for (const k in profileSystemNotices) {
                profileNotice = profileSystemNotices[k];
                type = profileNotice.PushType;

                if (isNeedValidRepeatMsg == false && profileNotice.NoticeSeq && profileNotice.NoticeSeq > noticeSeq) {
                    noticeSeq = profileNotice.NoticeSeq;
                }
                notify = { Type: type };
                switch (type) {
                    case PROFILE_NOTICE_TYPE.PROFILE_MODIFY:
                        notify.Profile_Account = profileNotice.Profile_Account;
                        notify.ProfileList = profileNotice.ProfileList;
                        break;
                    default:
                        log.error(`未知资料系统通知类型：profileNotice=${JSON.stringify(profileNotice)}`);
                        break;
                }

                if (isNeedValidRepeatMsg) {
                    if (type == PROFILE_NOTICE_TYPE.PROFILE_MODIFY) {
                        if (onProfileSystemNotifyCallbacks[type]) onProfileSystemNotifyCallbacks[type](notify);
                    }
                } else if (onProfileSystemNotifyCallbacks[type]) onProfileSystemNotifyCallbacks[type](notify);
            }
        };


        const handlerGroupSystemMsg = function (groupTip) {
            const groupReportTypeMsg = groupTip.MsgBody;
            const reportType = groupReportTypeMsg.ReportType;
            const toAccount = groupTip.GroupInfo.To_Account;


            const notify = {
                SrcFlag: 1,
                ReportType: reportType,
                GroupId: groupTip.ToGroupId,
                GroupName: groupTip.GroupInfo.GroupName,
                Operator_Account: groupReportTypeMsg.Operator_Account,
                MsgTime: groupTip.MsgTimeStamp,
            };
            switch (reportType) {
                case GROUP_SYSTEM_TYPE.JOIN_GROUP_REQUEST:
                    notify.RemarkInfo = groupReportTypeMsg.RemarkInfo;
                    notify.MsgKey = groupReportTypeMsg.MsgKey;
                    notify.Authentication = groupReportTypeMsg.Authentication;
                    notify.UserDefinedField = groupTip.UserDefinedField;
                    notify.From_Account = groupTip.From_Account;
                    notify.MsgSeq = groupTip.ClientSeq;
                    notify.MsgRandom = groupTip.MsgRandom;
                    break;
                case GROUP_SYSTEM_TYPE.JOIN_GROUP_ACCEPT:
                case GROUP_SYSTEM_TYPE.JOIN_GROUP_REFUSE:
                    notify.RemarkInfo = groupReportTypeMsg.RemarkInfo;
                    break;
                case GROUP_SYSTEM_TYPE.KICK:
                case GROUP_SYSTEM_TYPE.DESTORY:
                case GROUP_SYSTEM_TYPE.CREATE:
                case GROUP_SYSTEM_TYPE.INVITED_JOIN_GROUP_REQUEST:
                case GROUP_SYSTEM_TYPE.QUIT:
                case GROUP_SYSTEM_TYPE.SET_ADMIN:
                case GROUP_SYSTEM_TYPE.CANCEL_ADMIN:
                case GROUP_SYSTEM_TYPE.REVOKE:
                    break;
                case GROUP_SYSTEM_TYPE.CUSTOM:
                    notify.MsgSeq = groupTip.MsgSeq;
                    notify.UserDefinedField = groupReportTypeMsg.UserDefinedField;
                    break;
                default:
                    log.error(`未知群系统消息类型：reportType=${reportType}`);
                    break;
            }

            if (onGroupSystemNotifyCallbacks[reportType]) onGroupSystemNotifyCallbacks[reportType](notify);
        };


        const handlerC2cNotifyMsgArray = function (arr) {
            for (let i = 0, l = arr.length; i < l; i++) {
                handlerC2cEventMsg(arr[i]);
            }
        };


        var handlerC2cEventMsg = function (notify) {
            const subType = notify.SubMsgType;
            switch (subType) {
                case C2C_EVENT_SUB_TYPE.READED:
                    break;
                default:
                    log.error(`未知C2c系统消息：reportType=${reportType}`);
                    break;
            }


            if (onC2cEventCallbacks[subType]) onC2cEventCallbacks[subType](notify);
        };


        this.longPolling = function (cbOk, cbErr) {
            const opts = {
                Timeout: longPollingDefaultTimeOut / 1000,
                Cookie: {
                    NotifySeq: notifySeq,
                    NoticeSeq: noticeSeq,
                },
            };
            if (LongPollingId) {
                opts.Cookie.LongPollingId = LongPollingId;
                doPolling();
            } else {
                proto_getLongPollingId({}, (resp) => {
                    LongPollingId = opts.Cookie.LongPollingId = resp.LongPollingId;

                    longPollingDefaultTimeOut = resp.Timeout > 60 ? longPollingDefaultTimeOut : resp.Timeout * 1000;
                    doPolling();
                });
            }

            function doPolling() {
                proto_longPolling(opts, (resp) => {
                    for (const i in resp.EventArray) {
                        const e = resp.EventArray[i];
                        switch (e.Event) {
                            case LONG_POLLINNG_EVENT_TYPE.C2C:

                                notifySeq = e.NotifySeq;
                                log.warn('longpolling: received new c2c msg');

                                MsgManager.syncMsgs();
                                break;
                            case LONG_POLLINNG_EVENT_TYPE.GROUP_COMMON:
                                log.warn('longpolling: received new group msgs');
                                handlerOrdinaryAndTipGroupMsgs(e.Event, e.GroupMsgArray);
                                break;
                            case LONG_POLLINNG_EVENT_TYPE.GROUP_TIP:
                                log.warn('longpolling: received new group tips');
                                handlerOrdinaryAndTipGroupMsgs(e.Event, e.GroupTips);
                                break;
                            case LONG_POLLINNG_EVENT_TYPE.GROUP_SYSTEM:
                                log.warn('longpolling: received new group system msgs');

                                handlerGroupSystemMsgs(e.GroupTips, false);
                                break;
                            case LONG_POLLINNG_EVENT_TYPE.FRIEND_NOTICE:
                                log.warn('longpolling: received new friend system notice');

                                handlerFriendSystemNotices(e.FriendListMod, false);
                                break;
                            case LONG_POLLINNG_EVENT_TYPE.PROFILE_NOTICE:
                                log.warn('longpolling: received new profile system notice');

                                handlerProfileSystemNotices(e.ProfileDataMod, false);
                                break;
                            case LONG_POLLINNG_EVENT_TYPE.C2C_COMMON:
                                noticeSeq = e.C2cMsgArray[0].NoticeSeq;

                                log.warn('longpolling: received new c2c_common msg', noticeSeq);
                                handlerOrdinaryAndTipC2cMsgs(e.Event, e.C2cMsgArray);
                                break;
                            case LONG_POLLINNG_EVENT_TYPE.C2C_EVENT:
                                noticeSeq = e.C2cNotifyMsgArray[0].NoticeSeq;
                                log.warn('longpolling: received new c2c_event msg');
                                handlerC2cNotifyMsgArray(e.C2cNotifyMsgArray);
                                break;
                            default:
                                log.error(`longpolling收到未知新消息类型: Event=${e.Event}`);
                                break;
                        }
                    }
                    const successInfo = {
                        ActionStatus: ACTION_STATUS.OK,
                        ErrorCode: 0,
                    };
                    updatecLongPollingStatus(successInfo);
                }, (err) => {
                    updatecLongPollingStatus(err);
                    if (cbErr) cbErr(err);
                });
            }
        };


        this.bigGroupLongPolling = function (cbOk, cbErr) {
            const opts = {
                StartSeq: bigGroupLongPollingStartSeq,
                HoldTime: bigGroupLongPollingHoldTime,
                Key: bigGroupLongPollingKey,
            };

            proto_bigGroupLongPolling(opts, (resp) => {
                const msgObjList = [];
                bigGroupLongPollingStartSeq = resp.NextSeq;
                bigGroupLongPollingHoldTime = resp.HoldTime;
                bigGroupLongPollingKey = resp.Key;

                if (resp.RspMsgList && resp.RspMsgList.length > 0) {
                    let msgCount = 0,
                        msgInfo,
                        event,
                        msg;
                    for (let i = resp.RspMsgList.length - 1; i >= 0; i--) {
                        msgInfo = resp.RspMsgList[i];


                        if (msgInfo.IsPlaceMsg || !msgInfo.From_Account || !msgInfo.MsgBody || msgInfo.MsgBody.length == 0) {
                            continue;
                        }

                        event = msgInfo.Event;
                        switch (event) {
                            case LONG_POLLINNG_EVENT_TYPE.GROUP_COMMON:
                                log.info('bigGroupLongPolling: return new group msg');
                                msg = handlerGroupMsg(msgInfo, false, false);
                                msg && msgObjList.push(msg);
                                msgCount += 1;
                                break;
                            case LONG_POLLINNG_EVENT_TYPE.GROUP_TIP:
                            case LONG_POLLINNG_EVENT_TYPE.GROUP_TIP2:
                                log.info('bigGroupLongPolling: return new group tip');
                                msg = handlerGroupMsg(msgInfo, false, false);
                                msg && msgObjList.push(msg);

                                break;
                            case LONG_POLLINNG_EVENT_TYPE.GROUP_SYSTEM:
                                log.info('bigGroupLongPolling: new group system msg');
                                handlerGroupSystemMsg(msgInfo);
                                break;
                            default:
                                log.error(`bigGroupLongPolling收到未知新消息类型: Event=${event}`);
                                break;
                        }
                    }
                    if (msgCount > 0) {
                        MsgManager.setBigGroupLongPollingMsgMap(msgInfo.ToGroupId, msgCount);
                        log.warn(`current bigGroupLongPollingMsgMap: ${JSON.stringify(bigGroupLongPollingMsgMap)}`);
                    }
                }
                curBigGroupLongPollingRetErrorCount = 0;

                const successInfo = {
                    ActionStatus: ACTION_STATUS.OK,
                    ErrorCode: CONNECTION_STATUS.ON,
                    ErrorInfo: 'connection is ok...',
                };
                ConnManager.callBack(successInfo);

                if (cbOk) cbOk(msgObjList);
                else if (onBigGroupMsgCallback) onBigGroupMsgCallback(msgObjList);


                bigGroupLongPollingOn && MsgManager.bigGroupLongPolling();
            }, (err) => {
                if (err.ErrorCode != longPollingTimeOutErrorCode) {
                    log.error(err.ErrorInfo);

                    curBigGroupLongPollingRetErrorCount++;
                }
                if (err.ErrorCode == longPollingKickedErrorCode) {
                    log.error('多实例登录，被kick');

                    if (onKickedEventCall) { onKickedEventCall(); }
                }

                if (curBigGroupLongPollingRetErrorCount < LONG_POLLING_MAX_RET_ERROR_COUNT) {
                    bigGroupLongPollingOn && MsgManager.bigGroupLongPolling();
                } else {
                    const errInfo = {
                        ActionStatus: ACTION_STATUS.FAIL,
                        ErrorCode: CONNECTION_STATUS.OFF,
                        ErrorInfo: 'connection is off',
                    };
                    ConnManager.callBack(errInfo);
                }
                if (cbErr) cbErr(err);
            }, bigGroupLongPollingHoldTime * 1000);
        };


        var updatecLongPollingStatus = function (errObj) {
            if (errObj.ErrorCode == 0 || errObj.ErrorCode == longPollingTimeOutErrorCode) {
                curLongPollingRetErrorCount = 0;
                longPollingOffCallbackFlag = false;
                let errorInfo;
                let isNeedCallback = false;
                switch (curLongPollingStatus) {
                    case CONNECTION_STATUS.INIT:
                        isNeedCallback = true;
                        curLongPollingStatus = CONNECTION_STATUS.ON;
                        errorInfo = 'create connection successfully(INIT->ON)';
                        break;
                    case CONNECTION_STATUS.ON:
                        errorInfo = 'connection is on...(ON->ON)';
                        break;
                    case CONNECTION_STATUS.RECONNECT:
                        curLongPollingStatus = CONNECTION_STATUS.ON;
                        errorInfo = 'connection is on...(RECONNECT->ON)';
                        break;
                    case CONNECTION_STATUS.OFF:
                        isNeedCallback = true;
                        curLongPollingStatus = CONNECTION_STATUS.RECONNECT;
                        errorInfo = 'reconnect successfully(OFF->RECONNECT)';
                        break;
                }
                const successInfo = {
                    ActionStatus: ACTION_STATUS.OK,
                    ErrorCode: curLongPollingStatus,
                    ErrorInfo: errorInfo,
                };
                isNeedCallback && ConnManager.callBack(successInfo);
                longPollingOn && MsgManager.longPolling();
            } else if (errObj.ErrorCode == longPollingKickedErrorCode) {
                log.error('多实例登录，被kick');
                if (onKickedEventCall) { onKickedEventCall(); }
            } else {
                curLongPollingRetErrorCount++;
                log.warn(`longPolling接口第${curLongPollingRetErrorCount}次报错: ${errObj.ErrorInfo}`);

                if (curLongPollingRetErrorCount <= LONG_POLLING_MAX_RET_ERROR_COUNT) {
                    setTimeout(startNextLongPolling, 100);
                } else {
                    curLongPollingStatus = CONNECTION_STATUS.OFF;
                    const errInfo = {
                        ActionStatus: ACTION_STATUS.FAIL,
                        ErrorCode: CONNECTION_STATUS.OFF,
                        ErrorInfo: 'connection is off',
                    };
                    longPollingOffCallbackFlag == false && ConnManager.callBack(errInfo);
                    longPollingOffCallbackFlag = true;
                    log.warn(`${longPollingIntervalTime}毫秒之后,SDK会发起新的longPolling请求...`);
                    setTimeout(startNextLongPolling, longPollingIntervalTime);
                }
            }
        };


        var handlerOrdinaryAndTipC2cMsgs = function (eventType, C2cMsgArray) {
            const notifyInfo = [];
            msgInfos = C2cMsgArray;


            for (const i in msgInfos) {
                const msgInfo = msgInfos[i];
                var isSendMsg,
                    id,
                    headUrl;
                if (msgInfo.From_Account == ctx.identifier) {
                    isSendMsg = true;
                    id = msgInfo.To_Account;
                    headUrl = '';
                } else {
                    isSendMsg = false;
                    id = msgInfo.From_Account;
                    headUrl = '';
                }
                let sess = MsgStore.sessByTypeId(SESSION_TYPE.C2C, id);
                if (!sess) {
                    sess = new Session(SESSION_TYPE.C2C, id, id, headUrl, 0, 0);
                }
                const msg = new Msg(sess, isSendMsg, msgInfo.MsgSeq, msgInfo.MsgRandom, msgInfo.MsgTimeStamp, msgInfo.From_Account);
                let msgBody = null;
                let msgContent = null;
                let msgType = null;
                for (const mi in msgInfo.MsgBody) {
                    msgBody = msgInfo.MsgBody[mi];
                    msgType = msgBody.MsgType;
                    switch (msgType) {
                        case MSG_ELEMENT_TYPE.TEXT:
                            msgContent = new Msg.Elem.Text(msgBody.MsgContent.Text);
                            break;
                        case MSG_ELEMENT_TYPE.FACE:
                            msgContent = new Msg.Elem.Face(
                                    msgBody.MsgContent.Index,
                                    msgBody.MsgContent.Data,
                                );
                            break;
                        case MSG_ELEMENT_TYPE.IMAGE:
                            msgContent = new Msg.Elem.Images(
                                    msgBody.MsgContent.UUID,
                                );
                            for (const j in msgBody.MsgContent.ImageInfoArray) {
                                const tempImg = msgBody.MsgContent.ImageInfoArray[j];
                                msgContent.addImage(
                                        new Msg.Elem.Images.Image(
                                            tempImg.Type,
                                            tempImg.Size,
                                            tempImg.Width,
                                            tempImg.Height,
                                            tempImg.URL,
                                        ),
                                    );
                            }
                            break;
                        case MSG_ELEMENT_TYPE.SOUND:
                            if (msgBody.MsgContent) {
                                msgContent = new Msg.Elem.Sound(
                                        msgBody.MsgContent.UUID,
                                        msgBody.MsgContent.Second,
                                        msgBody.MsgContent.Size,
                                        msgInfo.From_Account,
                                        msgInfo.To_Account,
                                        msgBody.MsgContent.Download_Flag,
                                        SESSION_TYPE.C2C,
                                    );
                            } else {
                                msgType = MSG_ELEMENT_TYPE.TEXT;
                                msgContent = new Msg.Elem.Text('[语音消息]下载地址解析出错');
                            }
                            break;
                        case MSG_ELEMENT_TYPE.LOCATION:
                            msgContent = new Msg.Elem.Location(
                                    msgBody.MsgContent.Longitude,
                                    msgBody.MsgContent.Latitude,
                                    msgBody.MsgContent.Desc,
                                );
                            break;
                        case MSG_ELEMENT_TYPE.FILE:
                        case `${MSG_ELEMENT_TYPE.FILE} `:
                            msgType = MSG_ELEMENT_TYPE.FILE;
                            if (msgBody.MsgContent) {
                                msgContent = new Msg.Elem.File(
                                        msgBody.MsgContent.UUID,
                                        msgBody.MsgContent.FileName,
                                        msgBody.MsgContent.FileSize,
                                        msgInfo.From_Account,
                                        msgInfo.To_Account,
                                        msgBody.MsgContent.Download_Flag,
                                        SESSION_TYPE.C2C,
                                    );
                            } else {
                                msgType = MSG_ELEMENT_TYPE.TEXT;
                                msgContent = new Msg.Elem.Text('[文件消息下载地址解析出错]');
                            }
                            break;
                        case MSG_ELEMENT_TYPE.CUSTOM:
                            try {
                                const data = JSON.parse(msgBody.MsgContent.Data);
                                if (data && data.userAction && data.userAction == FRIEND_WRITE_MSG_ACTION.ING) {
                                    continue;
                                }
                            } catch (e) {
                            }

                            msgType = MSG_ELEMENT_TYPE.CUSTOM;
                            msgContent = new Msg.Elem.Custom(
                                    msgBody.MsgContent.Data,
                                    msgBody.MsgContent.Desc,
                                    msgBody.MsgContent.Ext,
                                );
                            break;
                        default :
                            msgType = MSG_ELEMENT_TYPE.TEXT;
                            msgContent = new Msg.Elem.Text(`web端暂不支持${msgBody.MsgType}消息`);
                            break;
                    }
                    msg.elems.push(new Msg.Elem(msgType, msgContent));
                }

                if (msg.elems.length > 0 && MsgStore.addMsg(msg)) {
                    notifyInfo.push(msg);
                }
            }
            if (notifyInfo.length > 0) { MsgStore.updateTimeline(); }
            if (notifyInfo.length > 0) {
                if (onMsgCallback) onMsgCallback(notifyInfo);
            }
        };


        var startNextLongPolling = function () {
            longPollingOn && MsgManager.longPolling();
        };


        const handlerApplyJoinGroupSystemMsgs = function (eventArray) {
            for (const i in eventArray) {
                const e = eventArray[i];
                switch (e.Event) {
                    case LONG_POLLINNG_EVENT_TYPE.GROUP_SYSTEM:
                        log.warn('handlerApplyJoinGroupSystemMsgs： handler new group system msg');

                        handlerGroupSystemMsgs(e.GroupTips, true);
                        break;
                    default:
                        log.error(`syncMsgs收到未知的群系统消息类型: Event=${e.Event}`);
                        break;
                }
            }
        };


        this.syncMsgs = function (cbOk, cbErr) {
            const notifyInfo = [];
            let msgInfos = [];

            proto_getMsgs(MsgStore.cookie, MsgStore.syncFlag, (resp) => {
                if (resp.SyncFlag == 2) {
                    MsgStore.syncFlag = 0;
                }

                msgInfos = resp.MsgList;
                MsgStore.cookie = resp.Cookie;

                for (const i in msgInfos) {
                    const msgInfo = msgInfos[i];
                    var isSendMsg,
                        id,
                        headUrl;
                    if (msgInfo.From_Account == ctx.identifier) {
                        isSendMsg = true;
                        id = msgInfo.To_Account;
                        headUrl = '';
                    } else {
                        isSendMsg = false;
                        id = msgInfo.From_Account;
                        headUrl = '';
                    }
                    let sess = MsgStore.sessByTypeId(SESSION_TYPE.C2C, id);
                    if (!sess) {
                        sess = new Session(SESSION_TYPE.C2C, id, id, headUrl, 0, 0);
                    }
                    const msg = new Msg(sess, isSendMsg, msgInfo.MsgSeq, msgInfo.MsgRandom, msgInfo.MsgTimeStamp, msgInfo.From_Account);
                    let msgBody = null;
                    let msgContent = null;
                    let msgType = null;
                    for (const mi in msgInfo.MsgBody) {
                        msgBody = msgInfo.MsgBody[mi];
                        msgType = msgBody.MsgType;
                        switch (msgType) {
                            case MSG_ELEMENT_TYPE.TEXT:
                                msgContent = new Msg.Elem.Text(msgBody.MsgContent.Text);
                                break;
                            case MSG_ELEMENT_TYPE.FACE:
                                msgContent = new Msg.Elem.Face(
                                    msgBody.MsgContent.Index,
                                    msgBody.MsgContent.Data,
                                );
                                break;
                            case MSG_ELEMENT_TYPE.IMAGE:
                                msgContent = new Msg.Elem.Images(
                                    msgBody.MsgContent.UUID,
                                );
                                for (const j in msgBody.MsgContent.ImageInfoArray) {
                                    const tempImg = msgBody.MsgContent.ImageInfoArray[j];
                                    msgContent.addImage(
                                        new Msg.Elem.Images.Image(
                                            tempImg.Type,
                                            tempImg.Size,
                                            tempImg.Width,
                                            tempImg.Height,
                                            tempImg.URL,
                                        ),
                                    );
                                }
                                break;
                            case MSG_ELEMENT_TYPE.SOUND:

                                if (msgBody.MsgContent) {
                                    msgContent = new Msg.Elem.Sound(
                                        msgBody.MsgContent.UUID,
                                        msgBody.MsgContent.Second,
                                        msgBody.MsgContent.Size,
                                        msgInfo.From_Account,
                                        msgInfo.To_Account,
                                        msgBody.MsgContent.Download_Flag,
                                        SESSION_TYPE.C2C,
                                    );
                                } else {
                                    msgType = MSG_ELEMENT_TYPE.TEXT;
                                    msgContent = new Msg.Elem.Text('[语音消息]下载地址解析出错');
                                }
                                break;
                            case MSG_ELEMENT_TYPE.LOCATION:
                                msgContent = new Msg.Elem.Location(
                                    msgBody.MsgContent.Longitude,
                                    msgBody.MsgContent.Latitude,
                                    msgBody.MsgContent.Desc,
                                );
                                break;
                            case MSG_ELEMENT_TYPE.FILE:
                            case `${MSG_ELEMENT_TYPE.FILE} `:
                                msgType = MSG_ELEMENT_TYPE.FILE;

                                if (msgBody.MsgContent) {
                                    msgContent = new Msg.Elem.File(
                                        msgBody.MsgContent.UUID,
                                        msgBody.MsgContent.FileName,
                                        msgBody.MsgContent.FileSize,
                                        msgInfo.From_Account,
                                        msgInfo.To_Account,
                                        msgBody.MsgContent.Download_Flag,
                                        SESSION_TYPE.C2C,
                                    );
                                } else {
                                    msgType = MSG_ELEMENT_TYPE.TEXT;
                                    msgContent = new Msg.Elem.Text('[文件消息下载地址解析出错]');
                                }
                                break;
                            case MSG_ELEMENT_TYPE.CUSTOM:
                                try {
                                    const data = JSON.parse(msgBody.MsgContent.Data);
                                    if (data && data.userAction && data.userAction == FRIEND_WRITE_MSG_ACTION.ING) {
                                        continue;
                                    }
                                } catch (e) {
                                }

                                msgType = MSG_ELEMENT_TYPE.CUSTOM;
                                msgContent = new Msg.Elem.Custom(
                                    msgBody.MsgContent.Data,
                                    msgBody.MsgContent.Desc,
                                    msgBody.MsgContent.Ext,
                                );
                                break;
                            default :
                                msgType = MSG_ELEMENT_TYPE.TEXT;
                                msgContent = new Msg.Elem.Text(`web端暂不支持${msgBody.MsgType}消息`);
                                break;
                        }
                        msg.elems.push(new Msg.Elem(msgType, msgContent));
                    }

                    if (msg.elems.length > 0 && MsgStore.addMsg(msg)) {
                        notifyInfo.push(msg);
                    }
                }


                handlerApplyJoinGroupSystemMsgs(resp.EventArray);

                if (notifyInfo.length > 0) { MsgStore.updateTimeline(); }
                if (cbOk) cbOk(notifyInfo);
                else if (notifyInfo.length > 0) {
                    if (onMsgCallback) onMsgCallback(notifyInfo);
                }
            }, (err) => {
                log.error(`getMsgs failed:${err.ErrorInfo}`);
                if (cbErr) cbErr(err);
            });
        };


        this.getC2CHistoryMsgs = function (options, cbOk, cbErr) {
            if (!options.Peer_Account) {
                if (cbErr) {
                    cbErr(tool.getReturnError('Peer_Account is empty', -13));
                    return;
                }
            }
            if (!options.MaxCnt) {
                options.MaxCnt = 15;
            }
            if (options.MaxCnt <= 0) {
                if (cbErr) {
                    cbErr(tool.getReturnError('MaxCnt should be greater than 0', -14));
                    return;
                }
            }
            if (options.MaxCnt > 15) {
                if (cbErr) {
                    cbErr(tool.getReturnError('MaxCnt can not be greater than 15', -15));
                    return;
                }
                return;
            }
            if (options.MsgKey == null || options.MsgKey === undefined) {
                options.MsgKey = '';
            }
            const opts = {
                Peer_Account: options.Peer_Account,
                MaxCnt: options.MaxCnt,
                LastMsgTime: options.LastMsgTime,
                MsgKey: options.MsgKey,
            };

            proto_getC2CHistoryMsgs(opts, (resp) => {
                const msgObjList = [];

                msgInfos = resp.MsgList;
                let sess = MsgStore.sessByTypeId(SESSION_TYPE.C2C, options.Peer_Account);
                if (!sess) {
                    sess = new Session(SESSION_TYPE.C2C, options.Peer_Account, options.Peer_Account, '', 0, 0);
                }
                for (const i in msgInfos) {
                    const msgInfo = msgInfos[i];
                    var isSendMsg,
                        id,
                        headUrl;
                    if (msgInfo.From_Account == ctx.identifier) {
                        isSendMsg = true;
                        id = msgInfo.To_Account;
                        headUrl = '';
                    } else {
                        isSendMsg = false;
                        id = msgInfo.From_Account;
                        headUrl = '';
                    }
                    const msg = new Msg(sess, isSendMsg, msgInfo.MsgSeq, msgInfo.MsgRandom, msgInfo.MsgTimeStamp, msgInfo.From_Account);
                    let msgBody = null;
                    let msgContent = null;
                    let msgType = null;
                    for (const mi in msgInfo.MsgBody) {
                        msgBody = msgInfo.MsgBody[mi];
                        msgType = msgBody.MsgType;
                        switch (msgType) {
                            case MSG_ELEMENT_TYPE.TEXT:
                                msgContent = new Msg.Elem.Text(msgBody.MsgContent.Text);
                                break;
                            case MSG_ELEMENT_TYPE.FACE:
                                msgContent = new Msg.Elem.Face(
                                    msgBody.MsgContent.Index,
                                    msgBody.MsgContent.Data,
                                );
                                break;
                            case MSG_ELEMENT_TYPE.IMAGE:
                                msgContent = new Msg.Elem.Images(
                                    msgBody.MsgContent.UUID,
                                );
                                for (const j in msgBody.MsgContent.ImageInfoArray) {
                                    const tempImg = msgBody.MsgContent.ImageInfoArray[j];
                                    msgContent.addImage(
                                        new Msg.Elem.Images.Image(
                                            tempImg.Type,
                                            tempImg.Size,
                                            tempImg.Width,
                                            tempImg.Height,
                                            tempImg.URL,
                                        ),
                                    );
                                }
                                break;
                            case MSG_ELEMENT_TYPE.SOUND:


                                if (msgBody.MsgContent) {
                                    msgContent = new Msg.Elem.Sound(
                                        msgBody.MsgContent.UUID,
                                        msgBody.MsgContent.Second,
                                        msgBody.MsgContent.Size,
                                        msgInfo.From_Account,
                                        msgInfo.To_Account,
                                        msgBody.MsgContent.Download_Flag,
                                        SESSION_TYPE.C2C,
                                    );
                                } else {
                                    msgType = MSG_ELEMENT_TYPE.TEXT;
                                    msgContent = new Msg.Elem.Text('[语音消息]下载地址解析出错');
                                }
                                break;
                            case MSG_ELEMENT_TYPE.LOCATION:
                                msgContent = new Msg.Elem.Location(
                                    msgBody.MsgContent.Longitude,
                                    msgBody.MsgContent.Latitude,
                                    msgBody.MsgContent.Desc,
                                );
                                break;
                            case MSG_ELEMENT_TYPE.FILE:
                            case `${MSG_ELEMENT_TYPE.FILE} `:
                                msgType = MSG_ELEMENT_TYPE.FILE;


                                if (msgBody.MsgContent) {
                                    msgContent = new Msg.Elem.File(
                                        msgBody.MsgContent.UUID,
                                        msgBody.MsgContent.FileName,
                                        msgBody.MsgContent.FileSize,
                                        msgInfo.From_Account,
                                        msgInfo.To_Account,
                                        msgBody.MsgContent.Download_Flag,
                                        SESSION_TYPE.C2C,
                                    );
                                } else {
                                    msgType = MSG_ELEMENT_TYPE.TEXT;
                                    msgContent = new Msg.Elem.Text('[文件消息下载地址解析出错]');
                                }
                                break;
                            case MSG_ELEMENT_TYPE.CUSTOM:
                                msgType = MSG_ELEMENT_TYPE.CUSTOM;
                                msgContent = new Msg.Elem.Custom(
                                    msgBody.MsgContent.Data,
                                    msgBody.MsgContent.Desc,
                                    msgBody.MsgContent.Ext,
                                );

                                break;
                            default :
                                msgType = MSG_ELEMENT_TYPE.TEXT;
                                msgContent = new Msg.Elem.Text(`web端暂不支持${msgBody.MsgType}消息`);
                                break;
                        }
                        msg.elems.push(new Msg.Elem(msgType, msgContent));
                    }
                    MsgStore.addMsg(msg);
                    msgObjList.push(msg);
                }

                MsgStore.updateTimeline();
                if (cbOk) {
                    const newResp = {
                        Complete: resp.Complete,
                        MsgCount: msgObjList.length,
                        LastMsgTime: resp.LastMsgTime,
                        MsgKey: resp.MsgKey,
                        MsgList: msgObjList,
                    };
                    sess.isFinished(resp.Complete);
                    cbOk(newResp);
                }
            }, (err) => {
                log.error(`getC2CHistoryMsgs failed:${err.ErrorInfo}`);
                if (cbErr) cbErr(err);
            });
        };


        this.syncGroupMsgs = function (options, cbOk, cbErr) {
            if (options.ReqMsgSeq <= 0) {
                if (cbErr) {
                    const errInfo = 'ReqMsgSeq must be greater than 0';
                    const error = tool.getReturnError(errInfo, -16);
                    cbErr(error);
                }
                return;
            }
            const opts = {
                GroupId: options.GroupId,
                ReqMsgSeq: options.ReqMsgSeq,
                ReqMsgNumber: options.ReqMsgNumber,
            };

            proto_getGroupMsgs(opts, (resp) => {
                const notifyInfo = [];
                const group_id = resp.GroupId;
                const msgInfos = resp.RspMsgList;
                const isFinished = resp.IsFinished;

                if (msgInfos == null || msgInfos === undefined) {
                    if (cbOk) {
                        cbOk([]);
                    }
                    return;
                }
                for (let i = msgInfos.length - 1; i >= 0; i--) {
                    const msgInfo = msgInfos[i];


                    if (msgInfo.IsPlaceMsg || !msgInfo.From_Account || !msgInfo.MsgBody || msgInfo.MsgBody.length == 0) {
                        continue;
                    }
                    const msg = handlerGroupMsg(msgInfo, true, true, isFinished);
                    if (msg) {
                        notifyInfo.push(msg);
                    }
                }
                if (notifyInfo.length > 0) { MsgStore.updateTimeline(); }
                if (cbOk) cbOk(notifyInfo);
                else if (notifyInfo.length > 0) {
                    if (onMsgCallback) onMsgCallback(notifyInfo);
                }
            }, (err) => {
                log.error(`getGroupMsgs failed:${err.ErrorInfo}`);
                if (cbErr) cbErr(err);
            });
        };


        var handlerGroupMsg = function (msgInfo, isSyncGroupMsgs, isAddMsgFlag, isFinished) {
            if (msgInfo.IsPlaceMsg || !msgInfo.From_Account || !msgInfo.MsgBody || msgInfo.MsgBody.length == 0) {
                return null;
            }
            let isSendMsg,
                id,
                headUrl,
                fromAccountNick;
            const group_id = msgInfo.ToGroupId;
            let group_name = group_id;
            if (msgInfo.GroupInfo) {
                if (msgInfo.GroupInfo.GroupName) {
                    group_name = msgInfo.GroupInfo.GroupName;
                }
            }

            fromAccountNick = msgInfo.From_Account;
            if (msgInfo.GroupInfo) {
                if (msgInfo.GroupInfo.From_AccountNick) {
                    fromAccountNick = msgInfo.GroupInfo.From_AccountNick;
                }
            }
            if (msgInfo.From_Account == ctx.identifier) {
                isSendMsg = true;
                id = msgInfo.From_Account;
                headUrl = '';
            } else {
                isSendMsg = false;
                id = msgInfo.From_Account;
                headUrl = '';
            }
            let sess = MsgStore.sessByTypeId(SESSION_TYPE.GROUP, group_id);
            if (!sess) {
                sess = new Session(SESSION_TYPE.GROUP, group_id, group_name, headUrl, 0, 0);
            }
            if (typeof isFinished !== 'undefined') {
                sess.isFinished(isFinished || 0);
            }
            let subType = GROUP_MSG_SUB_TYPE.COMMON;

            if (LONG_POLLINNG_EVENT_TYPE.GROUP_TIP == msgInfo.Event || LONG_POLLINNG_EVENT_TYPE.GROUP_TIP2 == msgInfo.Event) {
                subType = GROUP_MSG_SUB_TYPE.TIP;
                const groupTip = msgInfo.MsgBody;
                msgInfo.MsgBody = [];
                msgInfo.MsgBody.push({
                    MsgType: MSG_ELEMENT_TYPE.GROUP_TIP,
                    MsgContent: groupTip,
                },
                );
            } else if (msgInfo.MsgPriority) {
                if (msgInfo.MsgPriority == GROUP_MSG_PRIORITY_TYPE.REDPACKET) {
                    subType = GROUP_MSG_SUB_TYPE.REDPACKET;
                } else if (msgInfo.MsgPriority == GROUP_MSG_PRIORITY_TYPE.LOVEMSG) {
                    subType = GROUP_MSG_SUB_TYPE.LOVEMSG;
                }
            }
            const msg = new Msg(sess, isSendMsg, msgInfo.MsgSeq, msgInfo.MsgRandom, msgInfo.MsgTimeStamp, msgInfo.From_Account, subType, fromAccountNick);
            let msgBody = null;
            let msgContent = null;
            let msgType = null;
            for (const mi in msgInfo.MsgBody) {
                msgBody = msgInfo.MsgBody[mi];
                msgType = msgBody.MsgType;
                switch (msgType) {
                    case MSG_ELEMENT_TYPE.TEXT:
                        msgContent = new Msg.Elem.Text(msgBody.MsgContent.Text);
                        break;
                    case MSG_ELEMENT_TYPE.FACE:
                        msgContent = new Msg.Elem.Face(
                            msgBody.MsgContent.Index,
                            msgBody.MsgContent.Data,
                        );
                        break;
                    case MSG_ELEMENT_TYPE.IMAGE:
                        msgContent = new Msg.Elem.Images(
                            msgBody.MsgContent.UUID,
                        );
                        for (const j in msgBody.MsgContent.ImageInfoArray) {
                            msgContent.addImage(
                                new Msg.Elem.Images.Image(
                                    msgBody.MsgContent.ImageInfoArray[j].Type,
                                    msgBody.MsgContent.ImageInfoArray[j].Size,
                                    msgBody.MsgContent.ImageInfoArray[j].Width,
                                    msgBody.MsgContent.ImageInfoArray[j].Height,
                                    msgBody.MsgContent.ImageInfoArray[j].URL,
                                ),
                            );
                        }
                        break;
                    case MSG_ELEMENT_TYPE.SOUND:
                        if (msgBody.MsgContent) {
                            msgContent = new Msg.Elem.Sound(
                                msgBody.MsgContent.UUID,
                                msgBody.MsgContent.Second,
                                msgBody.MsgContent.Size,
                                msgInfo.From_Account,
                                msgInfo.To_Account,
                                msgBody.MsgContent.Download_Flag,
                                SESSION_TYPE.GROUP,
                            );
                        } else {
                            msgType = MSG_ELEMENT_TYPE.TEXT;
                            msgContent = new Msg.Elem.Text('[语音消息]下载地址解析出错');
                        }
                        break;
                    case MSG_ELEMENT_TYPE.LOCATION:
                        msgContent = new Msg.Elem.Location(
                            msgBody.MsgContent.Longitude,
                            msgBody.MsgContent.Latitude,
                            msgBody.MsgContent.Desc,
                        );
                        break;
                    case MSG_ELEMENT_TYPE.FILE:
                    case `${MSG_ELEMENT_TYPE.FILE} `:
                        msgType = MSG_ELEMENT_TYPE.FILE;
                        var fileUrl = getFileDownUrl(msgBody.MsgContent.UUID, msgInfo.From_Account, msgBody.MsgContent.FileName);

                        if (msgBody.MsgContent) {
                            msgContent = new Msg.Elem.File(
                                msgBody.MsgContent.UUID,
                                msgBody.MsgContent.FileName,
                                msgBody.MsgContent.FileSize,
                                msgInfo.From_Account,
                                msgInfo.To_Account,
                                msgBody.MsgContent.Download_Flag,
                                SESSION_TYPE.GROUP,
                            );
                        } else {
                            msgType = MSG_ELEMENT_TYPE.TEXT;
                            msgContent = new Msg.Elem.Text('[文件消息]地址解析出错');
                        }
                        break;
                    case MSG_ELEMENT_TYPE.GROUP_TIP:
                        var opType = msgBody.MsgContent.OpType;
                        msgContent = new Msg.Elem.GroupTip(
                            opType,
                            msgBody.MsgContent.Operator_Account,
                            group_id,
                            msgInfo.GroupInfo.GroupName,
                            msgBody.MsgContent.List_Account,
                        );
                        if (GROUP_TIP_TYPE.JOIN == opType || GROUP_TIP_TYPE.QUIT == opType) {
                            msgContent.setGroupMemberNum(msgBody.MsgContent.MemberNum);
                        } else if (GROUP_TIP_TYPE.MODIFY_GROUP_INFO == opType) {
                            let tempIsCallbackFlag = false;
                            const tempNewGroupInfo = {
                                GroupId: group_id,
                                GroupFaceUrl: null,
                                GroupName: null,
                                OwnerAccount: null,
                                GroupNotification: null,
                                GroupIntroduction: null,
                            };
                            const msgGroupNewInfo = msgBody.MsgContent.MsgGroupNewInfo;
                            if (msgGroupNewInfo.GroupFaceUrl) {
                                const tmpNGIFaceUrl = new Msg.Elem.GroupTip.GroupInfo(
                                    GROUP_TIP_MODIFY_GROUP_INFO_TYPE.FACE_URL,
                                    msgGroupNewInfo.GroupFaceUrl,
                                );
                                msgContent.addGroupInfo(tmpNGIFaceUrl);
                                tempIsCallbackFlag = true;
                                tempNewGroupInfo.GroupFaceUrl = msgGroupNewInfo.GroupFaceUrl;
                            }
                            if (msgGroupNewInfo.GroupName) {
                                const tmpNGIName = new Msg.Elem.GroupTip.GroupInfo(
                                    GROUP_TIP_MODIFY_GROUP_INFO_TYPE.NAME,
                                    msgGroupNewInfo.GroupName,
                                );
                                msgContent.addGroupInfo(tmpNGIName);
                                tempIsCallbackFlag = true;
                                tempNewGroupInfo.GroupName = msgGroupNewInfo.GroupName;
                            }
                            if (msgGroupNewInfo.Owner_Account) {
                                const tmpNGIOwner = new Msg.Elem.GroupTip.GroupInfo(
                                    GROUP_TIP_MODIFY_GROUP_INFO_TYPE.OWNER,
                                    msgGroupNewInfo.Owner_Account,
                                );
                                msgContent.addGroupInfo(tmpNGIOwner);
                                tempIsCallbackFlag = true;
                                tempNewGroupInfo.OwnerAccount = msgGroupNewInfo.Owner_Account;
                            }
                            if (msgGroupNewInfo.GroupNotification) {
                                const tmpNGINotification = new Msg.Elem.GroupTip.GroupInfo(
                                    GROUP_TIP_MODIFY_GROUP_INFO_TYPE.NOTIFICATION,
                                    msgGroupNewInfo.GroupNotification,
                                );
                                msgContent.addGroupInfo(tmpNGINotification);
                                tempIsCallbackFlag = true;
                                tempNewGroupInfo.GroupNotification = msgGroupNewInfo.GroupNotification;
                            }
                            if (msgGroupNewInfo.GroupIntroduction) {
                                const tmpNGIIntroduction = new Msg.Elem.GroupTip.GroupInfo(
                                    GROUP_TIP_MODIFY_GROUP_INFO_TYPE.INTRODUCTION,
                                    msgGroupNewInfo.GroupIntroduction,
                                );
                                msgContent.addGroupInfo(tmpNGIIntroduction);
                                tempIsCallbackFlag = true;
                                tempNewGroupInfo.GroupIntroduction = msgGroupNewInfo.GroupIntroduction;
                            }


                            if (isSyncGroupMsgs == false && tempIsCallbackFlag && onGroupInfoChangeCallback) {
                                onGroupInfoChangeCallback(tempNewGroupInfo);
                            }
                        } else if (GROUP_TIP_TYPE.MODIFY_MEMBER_INFO == opType) {
                            const memberInfos = msgBody.MsgContent.MsgMemberInfo;
                            for (const n in memberInfos) {
                                const memberInfo = memberInfos[n];
                                msgContent.addMemberInfo(
                                    new Msg.Elem.GroupTip.MemberInfo(
                                        memberInfo.User_Account, memberInfo.ShutupTime,
                                    ),
                                );
                            }
                        }
                        break;
                    case MSG_ELEMENT_TYPE.CUSTOM:
                        msgType = MSG_ELEMENT_TYPE.CUSTOM;
                        msgContent = new Msg.Elem.Custom(
                            msgBody.MsgContent.Data,
                            msgBody.MsgContent.Desc,
                            msgBody.MsgContent.Ext,
                        );
                        break;
                    default :
                        msgType = MSG_ELEMENT_TYPE.TEXT;
                        msgContent = new Msg.Elem.Text(`web端暂不支持${msgBody.MsgType}消息`);
                        break;
                }
                msg.elems.push(new Msg.Elem(msgType, msgContent));
            }

            if (isAddMsgFlag == false) {
                return msg;
            }

            if (MsgStore.addMsg(msg)) {
                return msg;
            }
            return null;
        };


        this.init = function (listeners, cbOk, cbErr) {
            if (!listeners.onMsgNotify) {
                log.warn('listeners.onMsgNotify is empty');
            }
            onMsgCallback = listeners.onMsgNotify;

            if (listeners.onBigGroupMsgNotify) {
                onBigGroupMsgCallback = listeners.onBigGroupMsgNotify;
            } else {
                log.warn('listeners.onBigGroupMsgNotify is empty');
            }

            if (listeners.onC2cEventNotifys) {
                onC2cEventCallbacks = listeners.onC2cEventNotifys;
            } else {
                log.warn('listeners.onC2cEventNotifys is empty');
            }
            if (listeners.onGroupSystemNotifys) {
                onGroupSystemNotifyCallbacks = listeners.onGroupSystemNotifys;
            } else {
                log.warn('listeners.onGroupSystemNotifys is empty');
            }
            if (listeners.onGroupInfoChangeNotify) {
                onGroupInfoChangeCallback = listeners.onGroupInfoChangeNotify;
            } else {
                log.warn('listeners.onGroupInfoChangeNotify is empty');
            }
            if (listeners.onFriendSystemNotifys) {
                onFriendSystemNotifyCallbacks = listeners.onFriendSystemNotifys;
            } else {
                log.warn('listeners.onFriendSystemNotifys is empty');
            }
            if (listeners.onProfileSystemNotifys) {
                onProfileSystemNotifyCallbacks = listeners.onProfileSystemNotifys;
            } else {
                log.warn('listeners.onProfileSystemNotifys is empty');
            }
            if (listeners.onKickedEventCall) {
                onKickedEventCall = listeners.onKickedEventCall;
            } else {
                log.warn('listeners.onKickedEventCall is empty');
            }

            if (listeners.onAppliedDownloadUrl) {
                onAppliedDownloadUrl = listeners.onAppliedDownloadUrl;
            } else {
                log.warn('listeners.onAppliedDownloadUrl is empty');
            }

            if (!ctx.identifier || !ctx.userSig) {
                if (cbOk) {
                    const success = {
                        ActionStatus: ACTION_STATUS.OK,
                        ErrorCode: 0,
                        ErrorInfo: 'login success(no login state)',
                    };
                    cbOk(success);
                }
                return;
            }


            initMyGroupMaxSeqs(
                (resp) => {
                    log.info('initMyGroupMaxSeqs success');

                    initIpAndAuthkey(
                        (initIpAndAuthkeyResp) => {
                            log.info('initIpAndAuthkey success');
                            if (cbOk) {
                                log.info('login success(have login state))');
                                const success = {
                                    ActionStatus: ACTION_STATUS.OK,
                                    ErrorCode: 0,
                                    ErrorInfo: 'login success',
                                };
                                cbOk(success);
                            }
                            MsgManager.setLongPollingOn(true);
                            longPollingOn && MsgManager.longPolling(cbOk);
                        }, cbErr);
                }, cbErr);
        };


        this.sendMsg = function (msg, cbOk, cbErr) {
            proto_sendMsg(msg, (resp) => {
                if (msg.sess.type() == SESSION_TYPE.C2C) {
                    if (!MsgStore.addMsg(msg)) {
                        const errInfo = 'sendMsg: addMsg failed!';
                        const error = tool.getReturnError(errInfo, -17);
                        log.error(errInfo);
                        if (cbErr) cbErr(error);
                        return;
                    }

                    MsgStore.updateTimeline();
                }
                if (cbOk) cbOk(resp);
            }, (err) => {
                if (cbErr) cbErr(err);
            });
        };
    }();


    const FileUploader = new function () {
        this.fileMd5 = null;

        const getFileMD5 = function (file, cbOk, cbErr) {
            let fileReader = null;
            try {
                fileReader = new FileReader();
            } catch (e) {
                if (cbErr) {
                    cbErr(tool.getReturnError('当前浏览器不支持FileReader', -18));
                    return;
                }
            }

            const blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;
            if (!blobSlice) {
                if (cbErr) {
                    cbErr(tool.getReturnError('当前浏览器不支持FileAPI', -19));
                    return;
                }
            }

            const chunkSize = 2 * 1024 * 1024;
            const chunks = Math.ceil(file.size / chunkSize);
            let currentChunk = 0;
            const spark = new SparkMD5();

            fileReader.onload = function (e) {
                let binaryStr = '';
                const bytes = new Uint8Array(e.target.result);
                const length = bytes.byteLength;
                for (let i = 0; i < length; i++) {
                    binaryStr += String.fromCharCode(bytes[i]);
                }
                spark.appendBinary(binaryStr);
                currentChunk++;
                if (currentChunk < chunks) {
                    loadNext();
                } else {
                    this.fileMd5 = spark.end();
                    if (cbOk) {
                        cbOk(this.fileMd5);
                    }
                }
            };

            function loadNext() {
                let start = currentChunk * chunkSize,
                    end = start + chunkSize >= file.size ? file.size : start + chunkSize;

                const b = blobSlice.call(file, start, end);


                fileReader.readAsArrayBuffer(b);
            }

            loadNext();
        };

        this.submitUploadFileForm = function (options, cbOk, cbErr) {
            let errInfo;
            let error;
            const formId = options.formId;
            const fileId = options.fileId;
            const iframeNum = uploadResultIframeId++;
            const iframeName = `uploadResultIframe_${iframeNum}`;
            const toAccount = options.To_Account;
            const businessType = options.businessType;

            const form = document.getElementById(formId);
            if (!form) {
                errInfo = `获取表单对象为空: formId=${formId}(formId非法)`;
                error = tool.getReturnError(errInfo, -20);
                if (cbErr) cbErr(error);
                return;
            }

            const fileObj = document.getElementById(fileId);
            if (!fileObj) {
                errInfo = `获取文件对象为空: fileId=${fileId}(没有选择文件或者fileId非法)`;
                error = tool.getReturnError(errInfo, -21);
                if (cbErr) cbErr(error);
                return;
            }

            fileObj.name = 'file';

            let iframe = document.createElement('iframe');
            iframe.name = iframeName;
            iframe.id = iframeName;
            iframe.style.display = 'none';
            document.body.appendChild(iframe);

            let cmdName;
            if (isAccessFormalEnv()) {
                cmdName = 'pic_up';
            } else {
                cmdName = 'pic_up_test';
            }
            const uploadApiUrl = 'https:';
            form.action = uploadApiUrl;
            form.method = 'post';

            form.target = iframeName;

            function createFormInput(name, value) {
                const tempInput = document.createElement('input');
                tempInput.type = 'hidden';
                tempInput.name = name;
                tempInput.value = value;
                form.appendChild(tempInput);
            }

            createFormInput('App_Version', VERSION_INFO.APP_VERSION);
            createFormInput('From_Account', ctx.identifier);
            createFormInput('To_Account', toAccount);
            createFormInput('Seq', nextSeq().toString());
            createFormInput('Timestamp', unixtime().toString());
            createFormInput('Random', createRandom().toString());
            createFormInput('Busi_Id', businessType);
            createFormInput('PkgFlag', UPLOAD_RES_PKG_FLAG.RAW_DATA.toString());
            createFormInput('Auth_Key', authkey);
            createFormInput('Server_Ver', VERSION_INFO.SERVER_VERSION.toString());
            createFormInput('File_Type', options.fileType);


            function checkFrameName() {
                let resp;
                try {
                    resp = JSON.parse(iframe.contentWindow.name) || {};
                } catch (e) {
                    resp = {};
                }
                if (resp.ActionStatus) {
                    iframe.src = 'about:blank';
                    iframe.parentNode.removeChild(iframe);
                    iframe = null;

                    if (resp.ActionStatus == ACTION_STATUS.OK) {
                        cbOk && cbOk(resp);
                    } else {
                        cbErr && cbErr(resp);
                    }
                } else {
                    setTimeout(checkFrameName, 100);
                }
            }

            setTimeout(checkFrameName, 500);

            form.submit();
        };

        this.uploadFile = function (options, cbOk, cbErr) {
            var file_upload = {

                init(options, cbOk, cbErr) {
                    const me = this;
                    me.file = options.file;

                    me.onProgressCallBack = options.onProgressCallBack;

                    if (options.abortButton) {
                        options.abortButton.onclick = me.abortHandler;
                    }
                    me.total = me.file.size;
                    me.loaded = 0;
                    me.step = 1080 * 1024;
                    me.sliceSize = 0;
                    me.sliceOffset = 0;
                    me.timestamp = unixtime();
                    me.seq = nextSeq();
                    me.random = createRandom();
                    me.fromAccount = ctx.identifier;
                    me.toAccount = options.To_Account;
                    me.fileMd5 = options.fileMd5;
                    me.businessType = options.businessType;
                    me.fileType = options.fileType;

                    me.cbOk = cbOk;
                    me.cbErr = cbErr;

                    me.reader = new FileReader();
                    me.blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;

                    me.reader.onloadstart = me.onLoadStart;
                    me.reader.onprogress = me.onProgress;
                    me.reader.onabort = me.onAbort;
                    me.reader.onerror = me.onerror;
                    me.reader.onload = me.onLoad;
                    me.reader.onloadend = me.onLoadEnd;
                },

                upload() {
                    const me = file_upload;

                    me.readBlob(0);
                },
                onLoadStart() {
                    const me = file_upload;
                },
                onProgress(e) {
                    const me = file_upload;
                    me.loaded += e.loaded;
                    if (me.onProgressCallBack) {
                        me.onProgressCallBack(me.loaded, me.total);
                    }
                },
                onAbort() {
                    const me = file_upload;
                },
                onError() {
                    const me = file_upload;
                },
                onLoad(e) {
                    const me = file_upload;
                    if (e.target.readyState == FileReader.DONE) {
                        let slice_data_base64 = e.target.result;

                        const pos = slice_data_base64.indexOf(',');
                        if (pos != -1) {
                            slice_data_base64 = slice_data_base64.substr(pos + 1);
                        }

                        const opt = {
                            From_Account: me.fromAccount,
                            To_Account: me.toAccount,
                            Busi_Id: me.businessType,
                            File_Type: me.fileType,
                            File_Str_Md5: me.fileMd5,
                            PkgFlag: UPLOAD_RES_PKG_FLAG.BASE64_DATA,
                            File_Size: me.total,
                            Slice_Offset: me.sliceOffset,
                            Slice_Size: me.sliceSize,
                            Slice_Data: slice_data_base64,
                            Seq: me.seq,
                            Timestamp: me.timestamp,
                            Random: me.random,
                        };


                        const succCallback = function (resp) {
                            if (resp.IsFinish == 0) {
                                me.loaded = resp.Next_Offset;
                                if (me.loaded < me.total) {
                                    me.readBlob(me.loaded);
                                } else {
                                    me.loaded = me.total;
                                }
                            } else if (me.cbOk) {
                                const tempResp = {
                                    ActionStatus: resp.ActionStatus,
                                    ErrorCode: resp.ErrorCode,
                                    ErrorInfo: resp.ErrorInfo,
                                    File_UUID: resp.File_UUID,
                                    File_Size: resp.Next_Offset,
                                    URL_INFO: resp.URL_INFO,
                                    Download_Flag: resp.Download_Flag,
                                };
                                if (me.fileType == UPLOAD_RES_TYPE.FILE) {
                                    tempResp.URL_INFO = getFileDownUrl(resp.File_UUID, ctx.identifier, me.file.name);
                                }
                                me.cbOk(tempResp);
                            }
                            Upload_Retry_Times = 0;
                        };

                        var errorCallback = function (resp) {
                            if (Upload_Retry_Times < Upload_Retry_Max_Times) {
                                Upload_Retry_Times++;
                                setTimeout(() => {
                                    proto_uploadPic(opt, succCallback, errorCallback);
                                }, 1000);
                            } else {
                                me.cbErr(resp);
                            }
                        };

                        proto_uploadPic(opt, succCallback, errorCallback);
                    }
                },
                onLoadEnd() {
                    const me = file_upload;
                },

                readBlob(start) {
                    const me = file_upload;
                    let blob,
                        file = me.file;
                    let end = start + me.step;
                    if (end > me.total) {
                        end = me.total;
                        me.sliceSize = end - start;
                    } else {
                        me.sliceSize = me.step;
                    }
                    me.sliceOffset = start;

                    blob = me.blobSlice.call(file, start, end);

                    me.reader.readAsDataURL(blob);
                },
                abortHandler() {
                    const me = file_upload;
                    if (me.reader) {
                        me.reader.abort();
                    }
                },
            };


            getFileMD5(options.file,
                (fileMd5) => {
                    log.info(`fileMd5: ${fileMd5}`);
                    options.fileMd5 = fileMd5;

                    file_upload.init(options, cbOk, cbErr);

                    file_upload.upload();
                },
                cbErr,
            );
        };
    }();


    webim.SESSION_TYPE = SESSION_TYPE;

    webim.MSG_MAX_LENGTH = MSG_MAX_LENGTH;


    webim.C2C_MSG_SUB_TYPE = C2C_MSG_SUB_TYPE;


    webim.GROUP_MSG_SUB_TYPE = GROUP_MSG_SUB_TYPE;


    webim.MSG_ELEMENT_TYPE = MSG_ELEMENT_TYPE;


    webim.GROUP_TIP_TYPE = GROUP_TIP_TYPE;


    webim.IMAGE_TYPE = IMAGE_TYPE;


    webim.GROUP_SYSTEM_TYPE = GROUP_SYSTEM_TYPE;


    webim.FRIEND_NOTICE_TYPE = FRIEND_NOTICE_TYPE;


    webim.GROUP_TIP_MODIFY_GROUP_INFO_TYPE = GROUP_TIP_MODIFY_GROUP_INFO_TYPE;


    webim.BROWSER_INFO = BROWSER_INFO;


    webim.Emotions = webim.EmotionPicData = emotions;

    webim.EmotionDataIndexs = webim.EmotionPicDataIndex = emotionDataIndexs;


    webim.TLS_ERROR_CODE = TLS_ERROR_CODE;


    webim.CONNECTION_STATUS = CONNECTION_STATUS;


    webim.UPLOAD_PIC_BUSSINESS_TYPE = UPLOAD_PIC_BUSSINESS_TYPE;


    webim.RECENT_CONTACT_TYPE = RECENT_CONTACT_TYPE;


    webim.UPLOAD_RES_TYPE = UPLOAD_RES_TYPE;


    webim.Tool = tool;

    webim.Log = log;


    webim.Msg = Msg;

    webim.Session = Session;

    webim.MsgStore = {
        sessMap() {
            return MsgStore.sessMap();
        },
        sessCount() {
            return MsgStore.sessCount();
        },
        sessByTypeId(type, id) {
            return MsgStore.sessByTypeId(type, id);
        },
        delSessByTypeId(type, id) {
            return MsgStore.delSessByTypeId(type, id);
        },
        resetCookieAndSyncFlag() {
            return MsgStore.resetCookieAndSyncFlag();
        },
    };

    webim.Resources = Resources;


    webim.login = webim.init = function (loginInfo, listeners, opts, cbOk, cbErr) {
        ConnManager.init(listeners.onConnNotify, cbOk, cbErr);


        if (listeners.jsonpCallback) jsonpCallback = listeners.jsonpCallback;

        _login(loginInfo, listeners, opts, cbOk, cbErr);
    };


    webim.logout = webim.offline = function (cbOk, cbErr) {
        return proto_logout('instance', cbOk, cbErr);
    };


    webim.logoutAll = function (cbOk, cbErr) {
        return proto_logout('all', cbOk, cbErr);
    };


    webim.sendMsg = function (msg, cbOk, cbErr) {
        return MsgManager.sendMsg(msg, cbOk, cbErr);
    };

    webim.syncMsgs = function (cbOk, cbErr) {
        return MsgManager.syncMsgs(cbOk, cbErr);
    };

    webim.getC2CHistoryMsgs = function (options, cbOk, cbErr) {
        return MsgManager.getC2CHistoryMsgs(options, cbOk, cbErr);
    };

    webim.syncGroupMsgs = function (options, cbOk, cbErr) {
        return MsgManager.syncGroupMsgs(options, cbOk, cbErr);
    };


    webim.c2CMsgReaded = function (options, cbOk, cbErr) {
        return MsgStore.c2CMsgReaded(options, cbOk, cbErr);
    };


    webim.groupMsgReaded = function (options, cbOk, cbErr) {
        return proto_groupMsgReaded(options, cbOk, cbErr);
    };


    webim.setAutoRead = function (selSess, isOn, isResetAll) {
        return MsgStore.setAutoRead(selSess, isOn, isResetAll);
    };


    webim.createGroup = function (options, cbOk, cbErr) {
        return proto_createGroup(options, cbOk, cbErr);
    };

    webim.createGroupHigh = function (options, cbOk, cbErr) {
        return proto_createGroupHigh(options, cbOk, cbErr);
    };

    webim.applyJoinGroup = function (options, cbOk, cbErr) {
        return proto_applyJoinGroup(options, cbOk, cbErr);
    };

    webim.handleApplyJoinGroupPendency = function (options, cbOk, cbErr) {
        return proto_handleApplyJoinGroupPendency(options, cbOk, cbErr);
    };


    webim.deleteApplyJoinGroupPendency = function (options, cbOk, cbErr) {
        return proto_deleteC2CMsg(options, cbOk, cbErr);
    };


    webim.quitGroup = function (options, cbOk, cbErr) {
        return proto_quitGroup(options, cbOk, cbErr);
    };

    webim.searchGroupByName = function (options, cbOk, cbErr) {
        return proto_searchGroupByName(options, cbOk, cbErr);
    };

    webim.getGroupPublicInfo = function (options, cbOk, cbErr) {
        return proto_getGroupPublicInfo(options, cbOk, cbErr);
    };

    webim.getGroupInfo = function (options, cbOk, cbErr) {
        return proto_getGroupInfo(options, cbOk, cbErr);
    };

    webim.modifyGroupBaseInfo = function (options, cbOk, cbErr) {
        return proto_modifyGroupBaseInfo(options, cbOk, cbErr);
    };

    webim.getGroupMemberInfo = function (options, cbOk, cbErr) {
        return proto_getGroupMemberInfo(options, cbOk, cbErr);
    };

    webim.addGroupMember = function (options, cbOk, cbErr) {
        return proto_addGroupMember(options, cbOk, cbErr);
    };

    webim.modifyGroupMember = function (options, cbOk, cbErr) {
        return proto_modifyGroupMember(options, cbOk, cbErr);
    };

    webim.deleteGroupMember = function (options, cbOk, cbErr) {
        return proto_deleteGroupMember(options, cbOk, cbErr);
    };

    webim.destroyGroup = function (options, cbOk, cbErr) {
        return proto_destroyGroup(options, cbOk, cbErr);
    };

    webim.changeGroupOwner = function (options, cbOk, cbErr) {
        return proto_changeGroupOwner(options, cbOk, cbErr);
    };


    webim.getJoinedGroupListHigh = function (options, cbOk, cbErr) {
        return proto_getJoinedGroupListHigh(options, cbOk, cbErr);
    };

    webim.getRoleInGroup = function (options, cbOk, cbErr) {
        return proto_getRoleInGroup(options, cbOk, cbErr);
    };

    webim.forbidSendMsg = function (options, cbOk, cbErr) {
        return proto_forbidSendMsg(options, cbOk, cbErr);
    };

    webim.sendCustomGroupNotify = function (options, cbOk, cbErr) {
        return proto_sendCustomGroupNotify(options, cbOk, cbErr);
    };


    webim.applyJoinBigGroup = function (options, cbOk, cbErr) {
        return proto_applyJoinBigGroup(options, cbOk, cbErr);
    };

    webim.quitBigGroup = function (options, cbOk, cbErr) {
        return proto_quitBigGroup(options, cbOk, cbErr);
    };


    webim.getProfilePortrait = function (options, cbOk, cbErr) {
        return proto_getProfilePortrait(options, cbOk, cbErr);
    };

    webim.setProfilePortrait = function (options, cbOk, cbErr) {
        return proto_setProfilePortrait(options, cbOk, cbErr);
    };

    webim.applyAddFriend = function (options, cbOk, cbErr) {
        return proto_applyAddFriend(options, cbOk, cbErr);
    };

    webim.getPendency = function (options, cbOk, cbErr) {
        return proto_getPendency(options, cbOk, cbErr);
    };

    webim.deletePendency = function (options, cbOk, cbErr) {
        return proto_deletePendency(options, cbOk, cbErr);
    };

    webim.responseFriend = function (options, cbOk, cbErr) {
        return proto_responseFriend(options, cbOk, cbErr);
    };

    webim.getAllFriend = function (options, cbOk, cbErr) {
        return proto_getAllFriend(options, cbOk, cbErr);
    };

    webim.deleteFriend = function (options, cbOk, cbErr) {
        return proto_deleteFriend(options, cbOk, cbErr);
    };

    webim.addBlackList = function (options, cbOk, cbErr) {
        return proto_addBlackList(options, cbOk, cbErr);
    };

    webim.deleteBlackList = function (options, cbOk, cbErr) {
        return proto_deleteBlackList(options, cbOk, cbErr);
    };

    webim.getBlackList = function (options, cbOk, cbErr) {
        return proto_getBlackList(options, cbOk, cbErr);
    };


    webim.getRecentContactList = function (options, cbOk, cbErr) {
        return proto_getRecentContactList(options, cbOk, cbErr);
    };


    webim.uploadFile = webim.uploadPic = function (options, cbOk, cbErr) {
        return FileUploader.uploadFile(options, cbOk, cbErr);
    };

    webim.submitUploadFileForm = function (options, cbOk, cbErr) {
        return FileUploader.submitUploadFileForm(options, cbOk, cbErr);
    };

    webim.uploadFileByBase64 = webim.uploadPicByBase64 = function (options, cbOk, cbErr) {
        const opt = {
            To_Account: options.toAccount,
            Busi_Id: options.businessType,
            File_Type: options.File_Type,
            File_Str_Md5: options.fileMd5,
            PkgFlag: UPLOAD_RES_PKG_FLAG.BASE64_DATA,
            File_Size: options.totalSize,
            Slice_Offset: 0,
            Slice_Size: options.totalSize,
            Slice_Data: options.base64Str,
            Seq: nextSeq(),
            Timestamp: unixtime(),
            Random: createRandom(),
        };
        return proto_uploadPic(opt, cbOk, cbErr);
    };


    webim.setJsonpLastRspData = function (rspData) {
        jsonpLastRspData = typeof (rspData) == 'string' ? JSON.parse(rspData) : rspData;
    };


    webim.getLongPollingId = function (options, cbOk, cbErr) {
        return proto_getLongPollingId(options, cbOk, cbErr);
    };


    webim.applyDownload = function (options, cbOk, cbErr) {
        return proto_applyDownload(options, cbOk, cbErr);
    };


    webim.onDownFile = function (uuid) {
        window.open(Resources.downloadMap[`uuid_${uuid}`]);
    };


    webim.checkLogin = function (cbErr, isNeedCallBack) {
        return checkLogin(cbErr, isNeedCallBack);
    };
}(webim));


module.exports = webim;
